Mybatis相关概念回顾
ORM:表示对象-关系映射的缩写。
Mybatis:一款基于ORM的半自动轻量级持久化框架,支持定制化SQL,存储过程以及高级映射。
Mybatis环境
Mybatis官网地址:
首先我们需要导入Mybatis的坐标
4.0.0
com.guslegend
MybatisQucickStart
1.0-SNAPSHOT
jar
MybatisQucickStart
http://maven.apache.org
UTF-8
UTF-8
11
11
11
org.mybatis
mybatis
3.4.5
mysql
mysql-connector-java
5.1.6
runtime
junit
junit
4.12
test
log4j
log4j
1.2.12
org.projectlombok
lombok
1.18.10
我们需要编写核心配置文件sqlMapConfig.xml
编写测试类
public class MybatisTest {
@Test
public void selectAll() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List userDoList = sqlSession.selectList("com.guslegend.mapper.UserMapper.findAll");
for (UserDo userDo : userDoList) {
System.out.println(userDo);
}
sqlSession.close();
}
}
Mybatis的CRUD
public class MybatisTest {
@Test
public void testUpdate() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserDo userDo = new UserDo();
userDo.setName("guslegend");
userDo.setId(1);
int update = sqlSession.update("com.guslegend.mapper.UserMapper.update", userDo);
System.out.println(update);
}
/**
* 测试删除
*/
@Test
public void testDel() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserDo userDo = new UserDo();
userDo.setName("guslegend");
userDo.setId(5);
int del = sqlSession.delete("com.guslegend.mapper.UserMapper.del", userDo);
System.out.println(del);
}
/**
* 测试查询
*/
@Test
public void select() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserDo userDo = sqlSession.selectOne("com.guslegend.mapper.UserMapper.findById", 1);
System.out.println(userDo);
}
/**
* 测试插入
*/
@Test
public void add() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserDo userDo = new UserDo();
userDo.setName("guslegend");
userDo.setId(5);
int insert = sqlSession.insert("com.guslegend.mapper.UserMapper.add", userDo);
System.out.println(insert);
}
/**
* 测试查询所有
*/
@Test
public void selectAll() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
List userDoList = sqlSession.selectList("com.guslegend.mapper.UserMapper.findAll");
for (UserDo userDo : userDoList) {
System.out.println(userDo);
}
sqlSession.close();
}
}
select * from user
insert into user values (#{id},#{name})
delete from user where id=#{id}
update user set name=#{name} where id=#{id}
select * from user where id=#{id}
注意:除了查询都需要开启事务 SqlSession sqlSession = sqlSessionFactory.openSession(true);
相关配置文件回顾
Mybatis相关API
InputStream inputStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
Resources类可以帮助我们在类路径下,文件系统中加载资源文件。
SqlSessionFactory里面有多方法创建SqlSession实例。
SqlSession可以进行语句的增删改查回顾事务,获取映射实例方法等。
执⾏语句的⽅法主要有:
T selectOne(String statement, Object parameter)
List selectList(String statement, Object parameter)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
操作事务的⽅法主要有:
void commit()
void rollback()
DAO层传统开发方式
UserMapper.java
public interface UserMapper {
List findAll() throws Exception;
}
UserMapperImpl.java
public class UserMapperImpl implements UserMapper {
@Override
public List findAll() throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
List userDoList = session.selectList("com.guslegend.mapper.UserMapper.findAll");
return userDoList;
}
}
测试传统方法
DAO层代理开发方式
Dao代理方式开发式企业主流的开发方式。这里Mapper层相当于Mapper接口。
-
Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同
-
Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同
-
Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
-
Mapper接⼝⽅法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
@Test public void testUserMapper2() throws Exception{ InputStream inputStream = Resources.getResourceAsStream("mapper/sqlMapConfig.xml"); SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List userDoList = userMapper.findAll(); for (UserDo userDo : userDoList) { System.out.println(userDo); } }
常见Mybatis配置解析
environments标签
其中,事务管理器(transactionManager)类型有两种:
•JDBC:这个配置就是直接使⽤了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作⽤域。
•MANAGED:这个配置⼏乎没做什么。它从来不提交或回滚⼀个连接,⽽是让容器来管理事务的整个⽣命周期
(⽐如 JEE 应⽤服务器的上下⽂)。 默认情况下它会关闭连接,然⽽⼀些容器并不希望这样,因此需要将
closeConnection 属性设置为 false 来阻⽌它默认的关闭⾏为。
其中,数据源(dataSource)类型有三种:
•UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。
•POOLED:这种数据源的实现利⽤“池”的概念将 JDBC 连接对象组织起来。
•JNDI:这个数据源的实现是为了能在如 EJB 或应⽤服务器这类容器中使⽤,容器可以集中或在外部配置数据源,
然后放置⼀个 JNDI 上下⽂的引⽤。
mapper标签
加载映射的方法
使⽤相对于类路径的资源引⽤,例如:
使⽤完全限定资源定位符(URL),例如:
使⽤映射器接⼝实现类的完全限定类名,例如:
将包内的映射器接⼝实现全部注册为映射器,例如:
Properties标签
在实际开发过程中,我们通常会将数据库配置信息单独抽取为一个properties文件。
typeAliases标签
Mybatis复杂映射关系
用户表和订单表的关系为:一个用户有多个订单,一个订单只属于一个用户。
一对一映射
@Data
public class OrderVO {
private Integer id;
private Double total;
private Date orderTime;
private UserDO userDO;
}
select * from orders o , user u where o.uid = u.id
@Test
public void test1() throws Exception{
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
List orderVOList = orderMapper.findAllOrderByOrderId();
for (OrderVO orderVO : orderVOList) {
System.out.println(orderVO);
}
}
OrderVO(id=1, total=50.0, orderTime=Wed Nov 12 00:00:00 CST 2025, userDO=UserDO(id=1, name=null))
OrderVO(id=2, total=100.0, orderTime=Thu Nov 13 00:00:00 CST 2025, userDO=UserDO(id=1, name=null))
OrderVO(id=4, total=1000.0, orderTime=Mon Dec 01 00:00:00 CST 2025, userDO=UserDO(id=2, name=null))
一对多映射
@Data
public class UserVO {
private Integer id;
private String name;
private List orders;
}
select * from user
select * from user u , orders o where u.id = o.uid
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List userVOList = userMapper.findAllOrderByUserId();
for (UserVO userVO : userVOList) {
System.out.println(userVO);
}
UserVO(id=1, name=guslegend, orders=[OrderDO(id=null, total=50.0, orderTime=Wed Nov 12 00:00:00 CST 2025), OrderDO(id=null, total=100.0, orderTime=Thu Nov 13 00:00:00 CST 2025)])
UserVO(id=2, name=xiaozhang, orders=[OrderDO(id=null, total=1000.0, orderTime=Mon Dec 01 00:00:00 CST 2025)])
多对多查询
@Data
public class UserVO {
private Integer id;
private String name;
private List roles;
}
select * from user u ,sys_role r , sys_user_role ur where u.id = ur.userid and r.id = ur.roleid
@Test
public void test3() throws Exception{
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List userVOList = userMapper.findAllUserAndRole();
for (UserVO userVO : userVOList) {
System.out.println(userVO);
}
}
UserVO(id=1, name=guslegend, orders=null, roles=[RoleDO(id=null, roleName=CTO), RoleDO(id=null, roleName=CEO)])
UserVO(id=2, name=xiaozhang, orders=null, roles=[RoleDO(id=null, roleName=CTO), RoleDO(id=null, roleName=CEO)])
Mybatis注解开发
注解开发的形式可以减少我们编写Mapper映射文件。
@Inset,@Update,@Delete,@Select,@Result(实现结果集封装),@Results(可以与@Result一起使用,封装多个结果集),@One(实现一对一结果集封装),@Many(实现一对多结果集封装)
增删改查
public interface UserMapper {
List findAll() throws Exception;
List findAllOrderByUserId() throws Exception;
List findAllUserAndRole() throws Exception;
@Insert("insert into user values (#{id}, #{name})")
int add(UserDO userDO) throws Exception;
@Delete("delete from user where id = #{id}")
int del(UserDO userDO) throws Exception;
@Update("update user set name = #{name} where id = #{id}")
int update(UserDO userDO) throws Exception;
@Select("select * from user where id = #{id}")
UserDO findById(Integer id) throws Exception;
}
public class MybatisTest2 {
private UserMapper userMapper;
@Before
public void before() throws Exception {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
SqlSession session = sqlSessionFactory.openSession(true);
userMapper = session.getMapper(UserMapper.class);
}
@Test
public void testAdd() throws Exception {
UserDO userDO = new UserDO();
userDO.setId(10);
userDO.setName("xiaozhangzhang");
int add = userMapper.add(userDO);
System.out.println(add);
}
@Test
public void testDel() throws Exception {
UserDO userDO = new UserDO();
userDO.setId(10);
int del = userMapper.del(userDO);
System.out.println(del);
}
@Test
public void testUpdate() throws Exception {
UserDO userDO = new UserDO();
userDO.setId(10);
userDO.setName("xiaozhangzhang2");
int update = userMapper.update(userDO);
System.out.println(update);
}
@Test
public void testFindById() throws Exception {
UserDO userDO = userMapper.findById(10);
System.out.println(userDO);
}
}
一对一查询
@Select("select * from orders")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "orderTime",column = "ordertime"),
@Result(property = "total",column = "total"),
@Result(property = "userDO",column = "uid",
javaType = UserDO.class, one = @One(select = "com.guslegend.mapper.UserMapper.findById"))
})
List findAll() throws Exception;
@Select("select * from user where id = #{id}")
UserDO findById(Integer id) throws Exception;
@Test
public void test1() throws Exception {
List orderVOList = orderMapper.findAll();
for (OrderVO orderVO : orderVOList){
System.out.println(orderVO);
}
}
一对多查询
@Select("select * from user")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "orders", column = "id",
javaType = List.class,
many = @Many(select = "com.guslegend.mapper.OrderMapper.findByUserId"))
})
List findAllUserAndOrder() throws Exception;
@Select("select * from orders where uid = #{id}")
List findByUserId(Integer id) throws Exception;
@Test
public void test2() throws Exception {
List userVOList = userMapper.findAllUserAndOrder();
for (UserVO userVO : userVOList){
System.out.println(userVO);
}
}
多对多查询
public interface RoleMapper {
@Select(" select * from sys_role r , sys_user_role ur where ur.userid =#{uid} and r.id = ur.roleid")
List findByUserId(int uid);
}
@Select("select * from user")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "roles", column = "id",
javaType = List.class,
many = @Many(select = "com.guslegend.mapper.RoleMapper.findByUserId"))
})
List findAllUserAndRole2();
@Test
public void test3() throws Exception {
List userVOList = userMapper.findAllUserAndRole2();
for (UserVO userVO : userVOList){
System.out.println(userVO);
}
}
Mybatis缓存
一级缓存
首先我们需要去打印一下sql日志方便我们去查看sql语句,所以要添加一个配置类log4j.properties
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout
我们查看控制台打印情况,发现只执行了一个sql。
然后我们只添加了一行sqlSession.clearCache(),就发现打印了两行sql语句。
通过以上实验,我们可以得出以下结论:
- 第一次发生查询语句的时候,我们先会从缓存中查询用户信息,没有查找到再从数据库中查询。
- 如果中间sqlSession去执行commit操作(增删改),则会清除sqlSession中的一级缓存,这样可以让缓存中存储的是最新的数据,避免脏读。
好了,接下来让我们带着一些列问题去阅读源码,来探究什么是一级缓存的原理。
- 一级缓存到底是什么?
- 一级缓存什么时候被创建?
- 一级缓存的工作流程又是怎么样的?
通过以上流程我们可以大概知道源码的阅读顺序是:
好,我们先在执行器Executor方法中查看发现
这里面最后一个environments是mybatis-config.xml中的标签。
我们追踪里面的query方法
二级缓存
一级缓存是基于sqlSession的,而二级缓存是基于mapper文件中的namespace的。
如何开启二级缓存:
和一级缓存默认开启不一样,二级缓存需要我们手动去开启。再全局配置文件sqlMapConfig.xml文件中加入如下代码:
然后再UserMapeer下开启二级缓存
开启二级缓存后,还需要将缓存pojo实现Serialiazble接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再去这个缓存的化,就需要反序列化了。所有mybatis中的pojo都要去实现Serializbale接口。
@Test
public void test() throws Exception {
InputStream inputStream =Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 第一次sql
UserDO u1 = userMapper1.findById(1);
System.out.println(u1);
sqlSession1.close();
// 第二次sql
UserDO u2 = userMapper2.findById(1);
System.out.println(u2);
sqlSession2.close();
}
可以观察到两个不同的sqlSession,第一个关闭了,第二个查询依然不发出sql查询语句。
mybatis中还可以配置useCache和flushCache等配置项,useCache是用来设置是否禁用二级缓存的,在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发送sql去查询,默认是true,即该sql使用二级缓存。
select * from user where id=#{id}
Redis整合二级缓存
为了实现在多台机器上都可以共享缓存,我们于是要采用分布式缓存,专门用来存储缓存数据的,这时候我们就想到了mybatis可以与redis结合。
添加mybatis-redis依赖
org.mybatis.caches
mybatis-redis
1.0.0-beta2
更改UserMapper里面的配置
select * from user
添加redis配置文件
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=123456
redis.database=1
Mybatis插件
- StatementHandler (prepare, parameterize, batch, update, query)
我们看到了可以拦截Executor接⼝的部分⽅法,⽐如update,query,commit,rollback等⽅法,还有其他接⼝
的⼀些⽅法等。
总体概括为: - 拦截执⾏器的⽅法
- 拦截参数的处理
- 拦截结果集的处理
- 拦截Sql语法构建的处理
拦截器示例:
package com.guslegend.interceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import java.util.Properties;
@Intercepts({@Signature(
type = Executor.class,
method = "update",
args = {MappedStatement.class, Object.class}
)})
public class MyInterceptor implements Interceptor {
@Override
public Object plugin(Object target) {
//创建target对象的代理对象,⽬的是将当前拦截器加⼊到该对象中
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//属性设置
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截方法,具体业务逻辑编写的位置
System.out.println("拦截方法");
return invocation.proceed();
}
}
注入到spring容器中
@Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}