MyBatis
MyBatis官方文档:https://mybatis.org/mybatis-3/zh/index.html
Maven依赖
org.mybatis
mybatis
x.x.x
XML配置
配置项(顺序固定)
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
properties(属性)
既可引入外部配置文件也可内部配置
config.properties:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=123456
...
加载顺序,后加载的覆盖先加载的
- properties 元素体内
- properties 引入
- 方法参数传递
类型别名(typeAliases)
为每个类设置别名
指定包名,没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名
内置别名
- 基本数据类型前面加上下划线如:
_int
对应int
- 引用数据类型小写如:
int
对应Integer
- 基本数据类型前面加上下划线如:
mappers(映射器)
引入资源方式
mapper文件
Maven静态资源过滤问题
当把xml文件放在src目录下时会出现
解决方法
src/main/java
**/*.properties
**/*.xml
true
结果集映射
简单结果集映射
多对一
所有学生(Student)对应同一个老师(Teacher)
按结果嵌套处理(常用)
按查询嵌套处理
一对多
一个老师拥有多个学生
按结果嵌套处理
按查询嵌套处理
动态SQL
if
choose (when, otherwise)
trim (where, set)
trim定制成where
...
update Author
username=#{username},
password=#{password},
email=#{email},
bio=#{bio}
where id=#{id}
trim定制成set
...
foreach
注解开发
简单映射
//Results可以用id命名,这里命名为studentMap,在其他方法中可以直接调用
@Select("select * from student")
@Results(id = "studentMap",value = {
@Result(id=true,column = "sid",property = "sid"),
@Result(column = "sname",property = "sname"),
@Result(column = "sex",property = "sex")
})
List findAll();
//直接调用已有的映射
@Select("select * from student where sid=#{id}")
@ResultMap("studentMap")
Student findById(Integer integer);
插入数据后返回id问题,两种方式
options,添加表的列名sid,指定成员变量的名id,设置useGeneratedKeys = true
selectkey,基本的思路和xml配置一样,代码编写如下:
@Insert("insert into student(sname,sex,birthday,cno) values(#{name},#{sex})") //@Options(useGeneratedKeys = true,keyColumn = "sid",keyProperty = "id") @SelectKey(statement = "select last_insert_id()",keyProperty = "id", keyColumn = "sid",resultType = int.class,before = false) void addStudent(Student student);
多对一
@One 注解(一对一)代替了
标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。
@One 注解属性介绍:
- select 指定用来多表查询的 sqlmapper
- fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。
使用格式:@Result(column=" ",property="",one=@One(select=""))
@Select("select * from account")
@Results(id="accountMap",
value= {
@Result(id=true,column="id",property="id"),
@Result(column="uid",property="uid"),
@Result(column="money",property="money"),
@Result(column="uid",
property="user",
one=@One(select="com.dao.IUserDao.findById",
fetchType=FetchType.LAZY)
)
})
List findAll();
@Select("select * from user where id = #{uid} ")
@ResultMap("userMap")
User findById(Integer userId);
一对多
@Many 注解(多对一)代替了
标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。
注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义;
使用格式:@Result(property="",column="",many=@Many(select=""))
@Select("select * from user")
@Results(id="userMap",
value= {
@Result(id=true,column="id",property="userId"),
@Result(column="username",property="userName"),
@Result(column="sex",property="userSex"),
@Result(column="id",property="accounts",
many=@Many(
select="com.dao.IAccountDao.findByUid",
fetchType=FetchType.LAZY
)
)
})
List findAll();
说明:
@Many:
相当于的配置
select 属性:代表将要执行的 sql 语句
fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
@Select("select * from account where uid = #{uid} ")
List findByUid(Integer userId);
二级缓存
@CacheNamespace(blocking=true)//mybatis 基于注解方式实现配置二级缓存
public interface IUserDao {}
动态SQL
script
要在带注解的映射器接口类中使用动态 SQL,可以使用 script 元素。比如:
@Update({""})
void updateAuthorValues(Author author);
bind
bind 元素可以从 OGNL 表达式中创建一个变量并将其绑定到上下文。比如:
SQL片段
提取SQL片段
title = #{title}
and author = #{author}
引用SQL片段:
缓存
一级缓存
SqlSession级别的缓存,也称为本地缓存,默认开启
一级缓存失效的的四种情况
- SqlSession不同
- SqlSession相同,查询条件不同
- SqlSession,两次查询之间执行了增删改操作
- SqlSession相同,手动清除一级缓存
clearCache()
二级缓存
使用步骤
在配置文件中开启全局缓存 【mybatis-config.xml】
在需要的mapper.xml中配置使用二级缓存【xxxMapper.xml】
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
配置二级缓存
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
知识点
MyBatis的优点?为什么用MyBatis而不用JDBC
解除SQL和代码的耦合,提供xml标签,支持编写动态SQL,支持对象与数据库的字段关系映射
#{}和${}的区别
#{}
是预编译处理,${}
是字符串替换- 处理
#{}
时,会将SQL中的#{}
替换为?号,调用PreparedStatement的set方法赋值,防止SQL注入 - 处理
${}
时,直接将${}
替换成变量的值 - 排序时使用order by 动态参数时需要注意,用$而不是#
模糊查询like语句写法
- 在Java代码中添加通配符
%
- 在sql中拼接
- 使用
${...}
:like '%${value}%'
- 使用
#{...}
:"%"#{value}"%"
- 使用
Mapper 接口的工作原理
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
如何获取自动生成的(主)键值?
如果采用主键自增长策略,自动生成的键值在insert执行完后被设置到传入的参数对象中,需要加上配置:
传递多个参数的方法
顺序传参
public User selectUser(String name, int deptId);
@Param注解传参
public User selectUser(@Param("userName") String name, @Param("deptId") int deptId);
Map传参
public User selectUser(Map
params); Java Bean传参
public User selectUser(User user);
结合Spring时一级缓存会失效
SqlSession交给了Spring管理,Spring在使用完后就会关闭
二级缓存的坑
二级缓存是mapper级别,当不同的mapper操作同一个数据时就会产生数据不一致
MyBatis原理
参考文章:MyBatis原理分析(通俗易懂)
如何构建一个线程安全的SqlSession
利用ThreadLocal获取或者关闭SqlSession对象,实现每一个线程都有自己的一个SqlSession对象。
TheadLocal 线程局部变量
ThreadLocal 的作用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
每个线程调用全局 ThreadLocal 对象的 set 方法,在 set 方法中,首先根据当前线程获取当前线程的 ThreadLocalMap 对象,然后往这个 map 中插入一条记录,key 其实是 ThreadLocal 对象,value 是各自的 set 方法传进去的值。也就是每个线程其实都有一份自己独享的 ThreadLocalMap对象,该对象的 Key 是 ThreadLocal 对象,值是用户设置的具体值。在线程结束时可以调用 ThreadLocal.remove()方法,这样会更快释放内存,不调,用也可以,因为线程结束后也可以自动释放相关的 ThreadLocal 变量。