mybatis
环境:
- JDK1.8
- MySQL8.0.22
- maven3.6.3
- IDEA
myba那个价tis是一款优秀的持久层框架,它支持定制化SQL、存储过程、高级映射。避免了几乎所有的JDBC代码和手动配置参数以及获取结果集。mybatis可以使用简单的xml或注解来配置和映射原生类型、接口和java的POJO类(Plain Old Java Object 普通老式java对象)为数据库中的记录。
mybatis入门程序
数据库准备
导入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
增加配置文件mybatis-config.xml
==注意:单纯的mybatis不能用通配符添加mapper.xml文件==
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/karottes?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/karotte/dao/mapper/karotterMapper.xml"/>
</mappers>
</configuration>
增加MybatisUtils工具类
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static{
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*获取SqlSession
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
//return sqlSessionFactory.openSession(true);//设置事务自动提交
}
}
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Karotter {
private int id;
private String name;
private String password;
private String sex;
private Date birthday;
private String address;
private String email;
private int age;
}
mapper接口
public interface KarotterMapper {
Karotter selectKarotterById(int id);
List<Karotter> getAll();
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.karotte.dao.mapper.KarotterMapper">
<select id="getAll" resultType="com.karotte.pojo.Karotter">
select * from karotter
</select>
<select id="selectKarotterById" resultType="com.karotte.pojo.Karotter">
select * from karotter where id = #{id}
</select>
</mapper>
测试类
public class KarotterTest {
@Test
public void getAllTest(){
try (SqlSession sqlSession = MybatisUtil.getSqlSession();) {
KarotterMapper mapper = sqlSession.getMapper(KarotterMapper.class);
List<Karotter> all = mapper.getAll();
all.forEach((karotter)->{
System.out.println(karotter);
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
此时运行会报错
Could not find resource com/karotte/dao/mapper/*.xml
maven由于约定大于配置,所以我们写的配置文件无法被导出或者生效,解决方案是pom文件中添加以下信息:
<build>
<resources>
<resource>
<directory>src/main/resource</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
此时会有==警告==,数据库驱动过时
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
Karotter(id=1, name=卡卡罗特, password=45678, sex=1, birthday=Sat Jun 18 03:30:00 CST 2022, address=达到三大, email=wuqing@163.com, age=28)
Karotter(id=2, name=贝吉塔, password=kaka098, sex=1, birthday=Sat Jun 18 03:33:56 CST 2022, address=贝吉塔行星, email=wuqing@163.com, age=28)
将mybatis-config.xml数据库驱动改成最新的即可
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
中文乱码
控制台报错:MalformedByteSequenceException: 2 字节的 UTF-8 序列的字节 2 无效。
在IDEA中将properties文件编码改为UTF-8即可。
点击File --> Settings --> Editor --> File Encodings
模糊查询
方式一:连接符$,sql注入,不安全
<select id="fuzzyQuery" resultType="com.bin.pojo.Book">
select * from mybatis.book where bookName like '%${info}%';
</select>
方式二:占位符#
<select id="fuzzyQuery" resultType="com.bin.pojo.Book">
select * from mybatis.book where bookName like
concat('%',#{info},'%');
</select>
总结:
- #{}是预编译处理,mybatis在处理#{}时,会将其替换成"?",再调用PreparedStatement的set方法来赋值。
- ${}是拼接字符串,将接收到的参数的内容不加任何修饰的拼接在SQL语句中,会引发SQL注入问题。
配置解析
核心配置文件(mybatis-config.xml)
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
配置元素前后顺序
properties、settings、typeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers
生命周期和作用域
生命周期和作用域是至关重要的,错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder
- 一旦创建了SqlSessionFactory,就不需要了
- 局部变量
SqlSessionFactory
- 可以看做是数据库连接池
- SqlSessionFactory一旦被创建就应该在运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例,因此SqlSessionFactory的最佳作用域是应用作用域
- 最简单的就是使用单例模式或者静态单例模式
SqlSession
- 连接到连接池的一个请求
- SqlSession的实例不是线程安全的,因此不能被共享,所以最佳作用域是请求或者方法作用域
- 用完之后就关闭,否则资源被占用
解决属性名和字段名不一致
sql中起别名
select id,name,`password` as pwd from karotter
resultMap-结果集映射
数据库字段类型和java类对应关系
JDBC Type | Java Type |
---|---|
CHAR | String |
VARCHAR | String |
LONGVARCHAR | String |
NUMERIC | java.math.BigDecimal |
DECIMAL | java.math.BigDecimal |
BIT | boolean |
BOOLEAN | boolean |
TINYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
CLOB | Clob |
BLOB | Blob |
ARRAY | Array |
DISTINCT | mapping of underlying type |
STRUCT | Struct |
REF | Ref |
DATALINK | java.net.URL[color=red][/color] |
<resultMap id="karotte" type="com.karotte.pojo.Karotter">
<result column="id" jdbcType="INTEGER" property="id" javaType="int" />
<result column="name" jdbcType="VARCHAR" property="name" javaType="String" />
<result column="password" jdbcType="VARCHAR" property="password" javaType="String" />
</resultMap>
<!-- 有中文怎么说 -->
<select id="getAll" resultMap="karotte">
select * from karotter
</select>
上述resultMap里面虽然只有三个,但是查询结果所有字段都会返回,所以说定义的时候***==只需要将不一致的字段显式定义即可==***。
Karotter(id=1, name=卡卡罗特, password=45678, sex=1, birthday=Sat Jun 18 03:30:00 CST 2022, address=北京通州, email=wuqing@163.com, age=28)
Karotter(id=2, name=贝吉塔, password=kaka098, sex=1, birthday=Sat Jun 18 03:33:56 CST 2022, address=贝吉塔行星, email=wuqing@163.com, age=28)
复杂例子
<resultMap id="detailedBlogMap" type="com.karotte.pojo.Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result column="blog_title" property="title"/>
<association property="author" javaType="com.karotte.pojo.Author">
<id column="author_id" property="id"/>
<result column="author_name" property="username"/>
<result column="author_password" property="password"/>
</association>
<collection property="posts" ofType="com.karotte.pojo.Post">
<id column="post_id" property="id"/>
<result column="post_subject" property="subject"/>
<discriminator javaType="int" column="draft">
<case value="1" resultType="com.karotte.pojo.DraftPost"/>
<case value="2" resultType="com.karotte.pojo.ApplePost"/>
</discriminator>
</collection>
</resultMap>
日志
日志工厂
配置文件中配即可
- SLF4J
- LOG4J
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- NO_LOGGING
- STDOUT_LOGGING:标准日志输出
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
log4j
- log4j是Apache的开源项目,通过使用log4j,我们可以控制日志信息传输到控制台、文件、GUI组件等。
- 控制每一条日志的输出格式
- 定义日志信息级别
- 通过一个配置文件来灵活的配置,不需要修改应用代码
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
log4j.properties
log4j.rootLogger=DEBUG,console,file
# 控制台(console)
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.err
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 日志文件(logFile)
log4j.appender.logFile=org.apache.log4j.FileAppender
log4j.appender.logFile.Threshold=DEBUG
log4j.appender.logFile.ImmediateFlush=true
log4j.appender.logFile.Append=true
log4j.appender.logFile.File=D:/logs/log.log4j
log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
log4j.appender.logFile.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
# 自定义Appender
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = corlin@cybercorlin.net
log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern=[%-5p] %d(%r) --> [%t] %l: %m %x %n
#日志的输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
import org.apache.log4j.Logger;
private static Logger logger = Logger.getLogger(KarotterTest.class);
日志级别
级别 | 描述 |
---|---|
ALL | 所有级别包括自定义级别。 |
DEBUG | 调试消息日志。 |
ERROR | 错误消息日志,应用程序可以继续运行。 |
FATAL | 严重错误消息日志,必须中止运行应用程序。 |
INFO | 信息消息。 |
OFF | 最高可能的排名,旨在关闭日志记录。 |
TRACE | 高于DEBUG。 |
WARN | 用于警告消息。 |
mybatis流程
动态sql
==只要最终的sql没有问题,随便拼==
<!--使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分 -->
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
<!-- 只是想从多个条件中选择一个使用。针对这种情况,MyBatis
提供了 choose 元素,它有点像 Java 中的 switch 语句。 -->
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
<!-- 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候) -->
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
<!--bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。 -->
<select id="selectBlogsLike" resultType="Blog">
<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
SELECT * FROM BLOG
WHERE title LIKE #{pattern}
</select>
<!--用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。 -->
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
sql片段
<sql id="kaka">
id,name,password
</sql>
<select id="getAll" resultMap="karotte">
select
<include refid="kaka"></include>
from karotter
</select>
缓存-了解,常用redis
mybatis缓存
- mybatis包含了一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存。缓存可以极大的提高查询效率。
- mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况只有一级缓存开启(SqlSession级别的缓存,也成为本地缓存)
- 二级缓存需要手动开启和配置,基于namespace的缓存
- 为了提高扩展性,mybatis定义了缓存接口Cache,我们可以通过实现Cache接口定义二级缓存
一级缓存
KarotterMapper mapper = sqlSession.getMapper(KarotterMapper.class);
List<Karotter> all = mapper.getAll();
List<Karotter> all1 = mapper.getAll();
sqlSession.clearCache();
List<Karotter> all2 = mapper.getAll();
List<Karotter> all3 = mapper.getAll();
//=======================================
[DEBUG] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137): ==> Preparing: select * from karotter
[DEBUG] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137): ==> Parameters:
[DEBUG] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137): <== Total: 12
[DEBUG] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137): ==> Preparing: select * from karotter
[DEBUG] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137): ==> Parameters:
[DEBUG] org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:137): <== Total: 12
一级缓存默认开启,只在一次sqlSession中有效,无法关闭
源码分析
SqlSession: 对外提供了用户和数据库之间交互需要的所有方法,隐藏了底层的细节。默认实现类是DefaultSqlSession。 Executor: SqlSession向用户提供操作数据库的方法,但和数据库操作有关的职责都会委托给Executor。Executor有两个实现类,和一级缓存关联的是BaseExecutor。 BaseExecutor: BaseExecutor是一个实现了Executor接口的抽象类,定义若干抽象方法,在执行的时候,把具体的操作委托给子类进行执行。 **PerpetualCache:**对Cache接口最基本实现,内部持有HashMap,对一级缓存的操作实则是对HashMap的操作。
一级缓存失效
- 同一个用户使用不同的SqlSession对象导致无法看到一级缓存工作
- 在一个SqlSession中使用条件查询不同一级缓存也会失效
- 在一个SqlSession使用相同条件,但是,此时在查询之间进行数据修改操作会导致一级缓存失效
- 在一个SqlSession使用相同查询条件此时手动刷新缓存时导致一级缓存失败
注意
- MyBatis一级缓存的生命周期和SqlSession一致
- MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺
- MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据
- mybatis和spring整合后进行mapper代理开发,不支持一级缓存
二级缓存
开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询
==当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库==。
二级缓存配置
-
需要在MyBatis核心配置文件,通过settings标签开发二级缓存。
<setting name="cacheEnabled" value="true"/>
-
在对应的Mapper文件中添加cache标签
-
cache标签属性
-
type:cache使用的类型,默认是PerpetualCache,这在一级缓存中提到过
-
eviction: 定义回收的策略,常见的有FIFO,LRU
-
flushInterval: 配置一定时间自动刷新缓存,单位是毫秒
-
size: 最多缓存对象的个数
-
readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化
-
blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存
-
cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache
<cache-ref namespace="mapper.StudentMapper"/>
参数详解参见:https://mybatis.net.cn/sqlmap-xml.html#cache
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> 可用的清除策略有: LRU – 最近最少使用:移除最长时间不被使用的对象。 FIFO – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。 WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 默认的清除策略是 LRU。
自定义缓存
<cache type="com.domain.something.MyCustomCache"/>
type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。
-