01、mybatis使用回顾

1、基本概念

MyBatis是⼀款优秀的基于ORM半⾃动轻量级持久层框架,它⽀持定制化SQL、存储过程以及⾼级映射。MyBatis避免了⼏乎所有的JDBC代码和⼿动设置参数以及获取结果集。MyBatis可以使⽤简单的XML或注解来配置和映射原⽣类型、接⼝和Java的POJO (Plain Old Java Objects,普通⽼式Java对 象)为数据库中的记录。
官网地址 : 官网地址

2、常用配置介绍使用

为了方面后续学习使用,先搭建一个maven工程,引入mybatis 依赖,另外因为要连接数据库,还需要引入jdbcdriver,为方便测试引入junit, 具体pom 配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>mybatis_quickstart</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
    </dependencies>

</project>

搭建项目结构如下,其中jdbc.properties 存储数据库连接配置信息(可以配置在sqlmapconfig中,但因为数据库信息每个环境都不一样,常见方式是单独放在与环境相关的文件中,为此单独放在jdbc.properties),sqlmapConfig.xml 是mybatis 核心配置文件,usermapper.xml是 mybatis sql映射配置文件存储信息
文件内容如下:
sqlmapConfig.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>
    <!--1、加载外部配置文件 -->
    <properties resource="jdbc.properties"></properties>

    <!--2、给实体类的全限定名起别名  -->
    <typeAliases>
        <!-- 给单独实体起别名 -->
<!--
        <typeAlias type="com.example.pojo.User" alias="user"></typeAlias>
-->
        <!-- 多个起别名  该包下所有类本身的类名 (不区分大小写)-->
        <package name="com.example.pojo"/>
    </typeAliases>

    <!--3、 指定当前默认的环境名称 -->
    <environments default="development">
        <!-- 3.1 可以配置不同的环境信息 -->
        <environment id="development">
            <!--3.1.1  当前事务交由jdbc  进行管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!--3.1.2  当前使用mybatis 提供的连接池 -->
            <dataSource type="POOLED">

                <!--<property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/de?useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>-->

                <!-- 数据源配置信息 -->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
	
	<!-- 方式1 -->
    <!--<mappers>
        <mapper resource="IOrderMapper.xml"></mapper>
    </mappers>-->
    
	<!-- 方式2 -->
    <!-- package 方式 引入需要配置文件与mapper 文件同包同名 -->
    <!--<mappers>
        <package name="com.example.mapper"/>
    </mappers>-->

	<!-- 方式3 -->
    <mappers>
        <mapper class="com.example.mapper.IUserMapper"></mapper>
    </mappers>
    
</configuration>

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/de?useUnicode=true&characterEncoding=UTF-8
jdbc.username=root
jdbc.password=root

usermapper.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.example.dao.IUserDao">

    <!--抽取sql  片段 -->
    <sql id="selectuser">
    select * from user
 </sql>
    <!-- namespace 命名空间,与id组成唯一标识 -->
    <select id="findAll" resultType="uSeR">
        <include refid="selectuser"></include>
    </select>

    <insert id="insertUser" parameterType="com.example.pojo.User">
        insert into user(id,username) values (#{id},#{username})
    </insert>

    <update id="updateUser" parameterType="com.example.pojo.User">
        update user set username=#{username} where id = #{id}
    </update>

    <delete id="deleteUser" parameterType="com.example.pojo.User">
        delete from  user where id = #{id}
    </delete>

    <select id="findUserByCondition" parameterType="user" resultType="user">
        select *  from  user where 1=1
        <if test="id !=null">
          and id = #{id}
        </if>
        <if test="username !=null">
        and  username = #{username}
        </if>
    </select>



    <!-- mybatis where 关键字会自动添加where关键字,并去掉第一个if条件里面的and  -->

    <select id="findUserByCondition2" parameterType="user" resultType="user">
        select *  from  user
        <where>
            <if test="id !=null">
               and  id = #{id}
            </if>
            <if test="username !=null">
                and  username = #{username}
            </if>
        </where>
    </select>


    <select id="findUserByIds" parameterType="list" resultType="user">
        select *  from  user
        <where>
            <foreach collection="list" open=" id in (" close=")" item="id" separator=",">
            #{id}
            </foreach>
        </where>
    </select>

</mapper>

User 类

package com.example.pojo;

public class User {

    private Integer id;

    private String username;

    //省略 get set 方法
}

至此,项目核心配置搭建完成
在这里插入图片描述
编写测试类

import java.util.List;

public class Mybayis_test {

    @Test
    public void test() throws Exception{
        //RESOURCE 工具类,配置文件加载,把配置文件加载到字节输入流
        InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
        // 2 \解析配置文件,并创建sql session工厂
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        //3、 生产sqlsession
        SqlSession sqlSession = sqlSessionFactory.openSession(); // 默认开启事务,但是 该事务不会自动提交,所以增删改 需要提交事务
        //设置为 true 自动提交事务
         //SqlSession sqlSession = sqlSessionFactory.openSession(true);

        //4 sqlsession 调用方法,查询所有 selectlist  查询一个 selectone  更新 update 删除 delete 添加 insert
        List<User> uSers=sqlSession.selectList("User.findAll");
        for (User user : uSers) {
            System.out.println(user);
        }
        sqlSession.close();

    }
}

如上面代码所示,通过mybatis实现单表的增删改查可以通过不同的标签,再通过mapper文件的namespace 以及sql id,指定到具体的一个sql ,mybatis帮我们实现参数转换和结果集封装。
有几个注意事项:
1、mappee.xml 中的paramtertype 指的是参数类型,retulttype 是返回值类型,两个都要写类的全限定名(别名例外,typealias )
2、sql 语句中,使用#{实体属性名}引用实体中的属性值
3、增删改涉及数据库变化,要使用sqlsession 提交事务,即sqlsession.commit()
4、通过sqlsession 提供的相关api完成对数据库的操作
5、映射mapper文件要有通用的文件头

按照经典三层模型数据结构,在我们企业实际开发中,通常会将和数据的交互放在dao层实现
这样,我们可以调整代码如下:
新建dao 层接口

public interface IUserDao {

    public List<User> findAll() throws Exception;
}

新建实现类,实现dao层接口

public class UserDaoImpl implements IUserDao {

    //传统开发方式
    @Override
    public List<User> findAll() throws Exception{

        //RESOURCE 工具类,配置文件加载,把配置文件加载到字节输入流
        InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
        // 2 \解析配置文件,并创建sql session工厂
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        //3、 生产sqlsession
        SqlSession sqlSession = sqlSessionFactory.openSession(); // 默认开启事务,但是 该事务不会自动提交,所以增删改 需要提交事务
        //设置为 true 自动提交事务
        //SqlSession sqlSession = sqlSessionFactory.openSession(true);
        //4 sqlsession 调用方法,查询所有 selectlist  查询一个 selectone  更新 update 删除 delete 添加 insert
        List<User> uSers=sqlSession.selectList("User.findAll");
        sqlSession.close();

        return uSers;
    }
}

测试

 @Test
    public void testdao() throws Exception{

        IUserDao userDao=new UserDaoImpl();
        List<User> all = userDao.findAll();
        for (User user : all) {
            System.out.println(user);
        }

    }

以上代码可以完成和数据库的正常交互,但是当有成百上千个交互要实现的时候,就会比较痛苦,每个sql 都要有自己的实现类,直观上看我们实现类的代码交互都类似,初始化相关解析配置信息,获取sqlsession, 获取对应的statementid,找到对应的脚本并执行,有事务的时候提交事务 为此,mybatis 提供了第二种方式:代理方式

采⽤ Mybatis 的代理开发⽅式实现 DAO 层的开发,这种⽅式是主流开发方式。

Mapper 接⼝开发⽅法只需要程序员编写Mapper 接⼝(相当于Dao 接⼝),由Mybatis 框架根据接⼝
定义创建接⼝的动态代理对象,代理对象的⽅法体同上边Dao接⼝实现类⽅法

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
    @Test
    public void testdao2() throws Exception{

        InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        List<User> all = mapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }

关于mybatis 动态sql 如if ,where ,foreach,sql 等标签的使用,可以参考上面的demo。

3、复杂映射

上面提到,当我们对单表操作时,通过sql 属性,resulttype指定对应的返回结果类型,这样mybatis 会自动帮我们做封装,那么问题来了,如果是对多个表操作,那么我们如果封装结果集,这里引入resultMap
这里我们以用户和订单的关系为例:
通常在我们电商系统中,一个用户有多笔订单,每个订单对应一个用户,如果这样,那么
新建订单类

package com.example.pojo;

public class Order {

    private Integer id;
    private String orderTime;
    private Double total;

    //表明该订单属于哪个用户
    private User user;

	//省略  get  set方法
}

订单表维护如下

DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ordertime` varchar(255) DEFAULT NULL,
  `total` double DEFAULT NULL,
  `uid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`),
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
3.1 一对一查询结果映射

如果我们查询订单对应的用户:

<?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.example.mapper.IOrderMapper">

    <resultMap id="userOrderMap" type="com.example.pojo.Order">
        <result property="id" column="id"></result>
        <result property="orderTime" column="orderTime"></result>
        <result property="total" column="total"></result>
        <association property="user" javaType="com.example.pojo.User" >
            <result property="id" column="uid"></result>
            <result property="username" column="username"></result>
        </association>
    </resultMap>

    <select id="findOrderAndUSer" resultMap="userOrderMap">
        select * from user u,orders o  where o.uid=u.id;
    </select>
</mapper>
public interface IOrderMapper {

    //查询订单的同时,查询所属的用户 一对一
    public List<Order> findOrderAndUSer();
}
3.2 一对多查询结果映射

一个人对应多个订单,为此:用户表新增订单列表集合

public class User implements Serializable {


    private Integer id;

    private String username;

    private List<Order> orderList;

    private List<Role> roleList;

	// 省略 set get  方法
}

核心代码如下

    <resultMap id="userOrderMap" type="com.example.pojo.User">
        <result property="id" column="id"></result>
        <result property="username" column="username"></result>
        <collection property="orderList" ofType="com.example.pojo.Order">
            <result property="id" column="oid"></result>
            <result property="orderTime" column="orderTime"></result>
            <result property="total" column="total"></result>
        </collection>
    </resultMap>


    <select id="findUserAndOrders" resultMap="userOrderMap">
        select u.*,o.id oid,o.ordertime,o.total,o.uid from user u left join orders o on u.id = o.uid
    </select>
    
3.3 多对多查询结果映射

在一个系统中有用户和角色两个模块,一个用户可以担任多个角色,一个角色可以有多个用户担任,为此,用户和角色是一对一的关系。如果要查询一个用户有哪些角色,为此
用户表新增角色list

package com.example.pojo;

import com.sun.org.apache.xpath.internal.operations.Or;

import javax.persistence.*;
import java.io.Serializable;
import java.util.List;

public class User implements Serializable {

    private Integer id;

    private String username;

    private List<Order> orderList;

    private List<Role> roleList;
	//省略get  set方法
}
    <resultMap id="userRoleMap" type="com.example.pojo.User">
        <result property="id" column="userid"></result>
        <result property="username" column="username"></result>
        <collection property="roleList" ofType="com.example.pojo.Role">
            <result property="id" column="roleid"></result>
            <result property="rolename" column="rolename"></result>
            <result property="roleDesc" column="roleDesc"></result>
        </collection>
    </resultMap>

    <select id="findUserAndRole" resultMap="userRoleMap">
        select * from user left join sys_user_role sur on user.id = sur.userid  left join sys_role sr on sur.roleid = sr.id
    </select>

以上可以发现,resultmap维护的就是sql 字段和 实体类的映射关系,这里要注意,因为常规开发中,我们经常列名是下划线命名,字段名是驼峰命名,为此,resultMap 在我们实际开发中实际上是一个很常用的标签。

4、注解开发

近年来随着注解开发越来越流行,mybatis也可以使用注解开发,不过这个只是个趋势,笔者自己的公司 实际上还是用的mapper 配置文件比较多,当时这种方式还是值得 学习了解。

4.1 使用注解完成简单增删改查

因为注解就不需要配置文件了,为此,调整mybatis sqlmap.xml mapper引入方式,调整后就能扫描到包下所有mapper

    <!--<mappers>
        <mapper resource="IOrderMapper.xml"></mapper>
    </mappers>-->

    <!-- package 方式 引入需要配置文件与mapper 文件同包同名 -->
    <mappers>
        <package name="com.example.mapper"/>
    </mappers>

Iusermapper 新增注解方法

@CacheNamespace (implementation = RedisCache.class)//开启二级缓存
public interface IUserMapper {


    //添加用户
    @Insert("insert into user value(#{id},#{username})")
    public void addUser(User user);

    //更新用户
    @Update("update user set username=#{username} where id=#{id}")
    public void updateUser(User user);

    @Select("select * from user")
    public List<User> selectUser();

    @Delete("delete from user where id=#{id}")
    public void deleteUser(User user);

    @Select("select * from user where id =#{id}")
    public User findUserById(Integer id);


}

测试类测试

  @Before
    public void befo() throws Exception{
        InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        userMapper  = sqlSession.getMapper(IUserMapper.class);
        orderMapper=sqlSession.getMapper(IOrderMapper.class);
    }

    @Test
    public void addUSer(){

        User user=new User();
        user.setId(6);
        user.setUsername("testadd");
        userMapper.addUser(user);
    }

    @Test
    public void updateUSer(){

        User user=new User();
        user.setId(6);
        user.setUsername("testudpate");
        userMapper.updateUser(user);
    }

    @Test
    public void deleteUSer(){

        User user=new User();
        user.setId(6);
        user.setUsername("testudpate");
        userMapper.deleteUser(user);
    }

    @Test
    public void selectUSer(){

        User user=new User();
        user.setId(1);
        user.setUsername("testudpate");
        userMapper.selectUser();
    }
4.2 使用注解实现复杂映射开发

实现复杂关系映射之前我们可以在映射⽂件中通过配置来实现,使⽤注解开发后,我们可以使⽤
@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置
在这里插入图片描述

  1. 一对一映射开发
    usermapper 新增注解查询方法
package com.example.mapper;

import com.example.pojo.Order;
import com.example.pojo.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface IOrderMapper {

    //查询订单的同时,查询所属的用户 一对一
    public List<Order> findOrderAndUSer();


    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "orderTime",column = "orderTime"),
            @Result(property = "total",column = "total"),
            @Result(property = "user",column = "uid",javaType = User.class,one = @One(select = "com.example.mapper.IUserMapper.findUserById"))
    })
    @Select("select * from orders")
    public List<Order> findOrderAndUSerbyAnnotation();


    @Options(useCache = true)  //使用二级缓存
    @Select("select * from orders where uid = #{id}")
    public List<Order> findOrderByUId(Integer id);
}
  1. 一对多,多对多
public interface IUserMapper {


  //查询所有用户信息,同时查询所有用户所属订单信息
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "orderList",column = "id",javaType = List.class,many = @Many(
                select = "com.example.mapper.IOrderMapper.findOrderByUId"
            ))

    })
    public List<User> findUserAndOrdersByAnnotation();


    //查询所有用户,同时查询所有用户关联角色信息
    @Select("select * from user")
    @Results({
            @Result(property = "id",column = "id"),
            @Result(property = "username",column = "username"),
            @Result(property = "roleList",column = "id",javaType = List.class,many = @Many(
                    select = "com.example.mapper.IRoleMapper.findRoleByid"
            ))

    })
    public List<User> findUserAndRoleByAnnotation();

}

public interface IRoleMapper {

    @Select("select sys_role.* from sys_role,sys_user_role where sys_role.id=sys_user_role.roleid and sys_user_role.userid=#{id}")
    public List<Role> findRoleByid(Integer id);
}
5、缓存原理
5.1 缓存概念

mybatis 提供了缓存机制,以便部分不会频繁更新的热点数据能更高效的查询,减少与数据库的交互次数,针对不同的数据有不同的缓存级别

5.2 一级缓存

一级缓存是 会话级别的,一级缓存又叫查询缓存,因为对同一个sqlSession,查询完执行 commit操作,会将缓存中的数据清空。一级缓存底层是一个map, value 是对象地址,一级缓存默认开启

Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
 BoundSql boundSql = ms.getBoundSql(parameter);
 //创建缓存
 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
 return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
 ...
 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
 //这个主要是处理存储过程⽤的。
 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
 } else {
 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key,
boundSql);
 }
 ...
// queryFromDatabase ⽅法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql
boundSql) throws SQLException {
 List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
 try {
 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 } finally {
 localCache.removeObject(key);
 }
 localCache.putObject(key, list);
 if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
 }
 return list; }
}

private Map<Object, Object> cache = new HashMap<Object, Object>();
 @Override
 public void putObject(Object key, Object value) { cache.put(key, value);
}
5.3 二级缓存

二级缓存是namespace 级别的,多个sqlSession可以共享⼀个mapper中的⼆级缓存区域
在这里插入图片描述
开启二级缓存,需要在sqlmapConfig.xml中加入如下配置:

<!--开启⼆级缓存-->
<settings>
 <setting name="cacheEnabled" value="true"/>
 </settings>

对应mapper文件中加入下面配置

<!--开启⼆级缓存-->
<cache></cache>

针对每次查询都需要最新的数据sql,要设置成useCache=false,禁⽤⼆级缓存,直接从数据库中获取。在mapper的同⼀个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如果不执⾏刷新缓存会出现脏读。设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使⽤缓存时如果⼿动修改数据库表中的查询数据会出现脏读。

mybatis除了默认实现,也对redis有个实现,pom 添加依赖

 <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-redis</artifactId>
            <version>1.0.0-beta2</version>
 </dependency>

resource 新增配置文件 redis.properties

host=localhost
port=6379
connectionTimeout=5000
password=
database=0

缓存注解调整,指定使用redis实现
@CacheNamespace (implementation = RedisCache.class)//开启二级缓存

mybatis-redis在存储数据的时候,是使⽤的hash结构,把cache的id作为这个hash
的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash
的field,需要缓存的内容直接使⽤SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责 对象
的序列化和反序列化;

底层结构默认开启级别存储额外处理
一级缓存hashmap会话级别对象地址(可以用==比较)
二级缓存hashmapnamespace对象拷贝存储对象实现序列号接口,sqlmapconfig.xml mapper文件要加额外配置,缓存才能生效,可以通过实现Cache 接口自定义缓存处理类(mybatis 有默认实现,也针对redis有个包)
6、插件机制

我们可基于MyBati s插件机制实现分⻚、分表,监控等功能。由于插件和业务⽆关,业务也⽆法感知插件的存在。因此可以⽆感植⼊插件,在⽆形中增强功能

Mybati s作为⼀个应⽤⼴泛的优秀的ORM开源框架,这个框架具有强⼤的灵活性,在四⼤组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易⽤的插件扩展机制。Mybatis对持久层的操作就是借助于四⼤核⼼对象。MyBatis⽀持⽤插件对四⼤核⼼对象进⾏拦截,对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的 动
态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象
在这里插入图片描述
MyBatis所允许拦截的⽅法如下:

执⾏器Executor (update、query、commit、rollback等⽅法);
SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等⽅ 法);
参数处理器ParameterHandler (getParameterObject、setParameters⽅法);
结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等⽅法);

6.1 插件原理

在四⼤对象创建的时候
1、每个创建出来的对象不是直接返回的,⽽是interceptorChain.pluginAll(parameterHandler);
2、获取到所有的Interceptor (拦截器)(插件需要实现的接⼝);调⽤ interceptor.plugin(target);返 回 target 包装后的对象
3、插件机制,我们可以使⽤插件为⽬标对象创建⼀个代理对象;AOP (⾯向切⾯)我们的插件可 以
为四⼤对象创建出代理对象,代理对象就可以拦截到四⼤对象的每⼀个执⾏;

public ParameterHandler newParameterHandler(MappedStatement mappedStatement,
                                                     Object object, BoundSql sql, InterceptorChain interceptorChain) {
        ParameterHandler parameterHandler =
                mappedStatement.getLang().createParameterHandler(mappedStatement, object, sql);
        parameterHandler = (ParameterHandler)
                interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
 }

 public Object pluginAll(Object target) {
     for (Interceptor interceptor : interceptors) {
         target = interceptor.plugin(target);
     }
     return target;
}

interceptorChain保存了所有的拦截器(interceptors),是mybatis初始化的时候创建的。调⽤拦截器链中的拦截器依次的对⽬标进⾏拦截或增强。interceptor.plugin(target)中的target就可以理解为mybatis中的四⼤对象。返回的target是被重重代理后的对象,如果我们想要拦截StatementHandler的prepare⽅法,那么可以这样定义插件:

package com.example.plugin;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Connection;
import java.util.Properties;


@Intercepts({//注意看这个⼤花括号,也就这说这⾥可以定义多个@Signature对多个地⽅拦截,都⽤
这个拦截器
        @Signature(type = StatementHandler.class,//这是指拦截哪个接⼝
                method = "prepare",//接⼝内的哪个⽅法名
                args = {Connection.class,Integer.class})//这是拦截的⽅法的⼊参,按
顺序写到这,不要多也不要少,如果⽅法重载,可是要通过⽅法名和⼊参来确定唯⼀的
})
public class MyPlugin implements Interceptor {


    /**
     * 只要被拦截的目标对象的目标方法 被执行时,每次都会执行intercept 方法
     **/
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强");
        return invocation.proceed(); //原方法执行
    }

    /**
     * 主要为了 把当前的拦截器 生成代理 存到拦截器链中
     * 包装⽬标对象 为⽬标对象创建代理对象
     * @Param target为要拦截的对象
 	 * @Return代理对象
     **/
    @Override
    public Object plugin(Object o) {

        Object wrap = Plugin.wrap(o, this);
        return wrap;
    }


    /**
        获取配置文件参数
        插件初始化的时候调⽤,也只调⽤⼀次,插件配置的属性从这⾥设置进来
     **/
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件到的参数"+properties);
    }
}

在sqlmapConfig.xml 中配置插件

    <plugins>
        <plugin interceptor="com.example.plugin.MyPlugin">
            <property name="name" value="tom"/>
        </plugin>
    </plugins>

这样MyBatis在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。
待准备⼯作做完后,MyBatis处于就绪状态。我们在执⾏SQL时,需要先通过DefaultSqlSessionFactory
创建 SqlSession。StatementHandler实例会在创建 SqlSession 的过程中被创建, StatementHandler实例创建完毕后,MyBatis会通过JDK动态代理为实例⽣成代理类。这样,插件逻辑即可在StatementHandler相关⽅法被调⽤前执⾏。

6.2 自定义插件

Mybatis 插件接⼝-Interceptor

  • Intercept⽅法,插件的核⼼⽅法
  • plugin⽅法,⽣成target的代理对象
  • setProperties⽅法,传递插件所需参数

示例代码如上

public class Plugin implements InvocationHandler {
    private final Object target;
    private final Interceptor interceptor;
    private final Map<Class<?>, Set<Method>> signatureMap;

    public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

从mybatis 源码可以看到,Plugin实现了invocationHandler接口,通过warp 接口返回代理对象,在我们调用目标对象的invoke 方法时,会判断方法有没有被代理,如果被代理,会调用对应拦截器的intercept方法,否则调用原方法

6.3 常用插件
6.3.1 pagehelper插件

MyBati s可以使⽤第三⽅的插件来对功能进⾏扩展,分⻚助⼿PageHelper是将分⻚的复杂操作进⾏封
装,使⽤简单的⽅式即可获得分⻚的相关数据
开发步骤:

  1. 导⼊通⽤PageHelper的坐标
  2. 在mybatis核⼼配置⽂件中配置PageHelper插件
  3. 测试分⻚数据获取

1、 导入坐标

 <dependency>
 <groupId>com.github.pagehelper</groupId>
 <artifactId>pagehelper</artifactId>
 <version>3.7.5</version>
 </dependency>
 <dependency>
 <groupId>com.github.jsqlparser</groupId>
 <artifactId>jsqlparser</artifactId>
 <version>0.9.1</version>
 </dependency>

2、配置pagehelper插件

 <!--注意:分⻚助⼿的插件 配置在通⽤馆mapper之前*-->*
 <plugin interceptor="com.github.pagehelper.PageHelper">
 <!—指定⽅⾔ —>
 <property name="dialect" value="mysql"/>
 </plugin>
    @Test
    public void pageHelperTest(){

        PageHelper.startPage(1,1);
        List<User> users = userMapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }

        PageInfo<User> pageInfo=new PageInfo<>(users);
        System.out.println("总条数"+pageInfo.getTotal());
        System.out.println("总页数"+pageInfo.getPages());
        System.out.println("当前页"+pageInfo.getPageNum());
        System.out.println("每页显示条数"+pageInfo.getPageSize());
    }
6.3.2 通用mapper 插件

通⽤Mapper就是为了解决单表增删改查,基于Mybatis的插件机制。开发⼈员不需要编写SQL,不需要
在DAO中增加⽅法,只要写好实体类,就能⽀持相应的增删改查⽅法

1、导入坐标

<dependency>
 <groupId>tk.mybatis</groupId>
 <artifactId>mapper</artifactId>
 <version>3.1.2</version>
</dependency>

2、配置插件

<plugins>
 <!--分⻚插件:如果有分⻚插件,要排在通⽤mapper之前-->
 <plugin interceptor="com.github.pagehelper.PageHelper">
 <property name="dialect" value="mysql"/>
 </plugin>
 <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
 <!-- 通⽤Mapper接⼝,多个通⽤接⼝⽤逗号隔开 -->
 <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
 </plugin>
</plugins>

3、实体类设置主键

@Table(name = "user")
public class User implements Serializable {

    //public long serialVersionUID =1;

    @Id //对应主键 id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键生成策略
    private Integer id;

    @Column // 列名和实体类名字 不一样时候,使用
    private String username;

	
}

这里演示个别框架封装的方法,其他方法类似

    @Test
    public void mappperTest() throws Exception{

        InputStream is= Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        UserMapper userMapper  = sqlSession.getMapper(UserMapper.class);

        User user=new User();
        user.setId(1);
         //(1)mapper基础接⼝
        //select 接⼝
        User user1 = userMapper.selectOne(user); //根据实体中的属性进⾏查询,只能有—个返回值
        List<User> users = userMapper.select(null); //查询全部结果
        userMapper.selectByPrimaryKey(1); //根据主键字段进⾏查询,⽅法参数必须包含完整的主键属性,查询条件使⽤等号
        userMapper.selectCount(user); //根据实体中的属性查询总数,查询条件使⽤等号
        // insert 接⼝
        int insert = userMapper.insert(user); //保存⼀个实体,null值也会保存,不会使⽤数据库默认值
        int i = userMapper.insertSelective(user); //保存实体,null的属性不会保存,会使⽤数据库默认值
        // update 接⼝
        int i1 = userMapper.updateByPrimaryKey(user);//根据主键更新实体全部字段,null值会被更新
        // delete 接⼝
        int delete = userMapper.delete(user); //根据实体属性作为条件进⾏删除,查询条件 使⽤等号
        userMapper.deleteByPrimaryKey(1); //根据主键字段进⾏删除,⽅法参数必须包含完整的主键属性
        //(2)example⽅法

        Example example=new Example(User.class);
        example.createCriteria().andEqualTo("id",1);

        List<User> users = userMapper.selectByExample(example);
        for (User user1 : users) {
            System.out.println(user1);
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值