Mybatis基础回顾和高级应用

20 阅读10分钟

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接口。

  1. Mapper.xml⽂件中的namespace与mapper接⼝的全限定名相同

  2. Mapper接⼝⽅法名和Mapper.xml中定义的每个statement的id相同

  3. Mapper接⼝⽅法的输⼊参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

  4. 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语句。

通过以上实验,我们可以得出以下结论:

  1. 第一次发生查询语句的时候,我们先会从缓存中查询用户信息,没有查找到再从数据库中查询。
  2. 如果中间sqlSession去执行commit操作(增删改),则会清除sqlSession中的一级缓存,这样可以让缓存中存储的是最新的数据,避免脏读。

好了,接下来让我们带着一些列问题去阅读源码,来探究什么是一级缓存的原理。

  1. 一级缓存到底是什么?
  2. 一级缓存什么时候被创建?
  3. 一级缓存的工作流程又是怎么样的?

通过以上流程我们可以大概知道源码的阅读顺序是:

好,我们先在执行器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插件

  1. StatementHandler (prepare, parameterize, batch, update, query)
    我们看到了可以拦截Executor接⼝的部分⽅法,⽐如update,query,commit,rollback等⽅法,还有其他接⼝
    的⼀些⽅法等。
    总体概括为:
  2. 拦截执⾏器的⽅法
  3. 拦截参数的处理
  4. 拦截结果集的处理
  5. 拦截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();
    }