SqlSessionFactory 的生成 MyBatis 是一个基于 SqlSessionFactory 构建的框架,对于 SqlSessionFactory 而言,它的作用是生成 SqlSession 接口,但是它们对于业务而言是单一的,在 Spring Boot 中,对于它们的实现是非显示的,去掉框架,我们可以这么做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import org.apache.ibatis.io.Resources;... private static SqlSessionFactory sqlSessionFactory;... try { Reader reader = Resources.getResourceAsReader("mybatis-config.xml" ); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); reader.close(); } catch (IOException ignore) { ignore.printStackTrace(); } ... SqlSession sqlSession = sqlSessionFactory.openSession(); try { List<Country> countryList = sqlSession.selectList("selectAll" ); printCountryList(countryList); } finally { sqlSession.close(); } ...
Mapper 接口动态代理 在使用 MyBatis 时,我们仅仅定义接口 mapper 和与之对应的 xml ,但是并不需要做具体的 imp 类与方法,这是因为 MyBatis 在内部存在一个代理者,实现了 InvocationHandler
接口,当我们使用某个 pojo 类的方法的时候,它将获得这个 pojo 类的全限定名称(同时,也是 xml 中的 namespace ,两者相互关联),与调用的方法进行组合,这样一个方法就被唯一指定了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ... public class MyMapperProxy <T > implements InvocationHandler { private Class<T> mapperInterface; private SqlSession sqlSession; public MyMapperProxy (Class<T> mapperInterface, SqlSession sqlSession) { this .mapperInterface = mapperInterface; this .sqlSession = sqlSession; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { List<T> list = sqlSession.selectList( mapperInterface.getCanonicalName() + "." + method.getName()); return list; } } ...
MyBatis 接口 sql 的实现 有三种方式可以实现接口中的 sql ,分别是 xml ,接口上注解,以及 provider 注解。
三种方式大同小异,为了代码一致性,推荐统一使用 xml 方式。
xml:
1 2 3 <select id ="selectById" resultMap ="userMap" > select * from sys_user where id = #{id} </select >
注解:
1 2 3 4 5 6 7 8 @Select ({ "select id, role_name roleName, enabled," + "create_by createBy," + "create_time createTime " + "from sys_role " + "where id = #{id}" }) SysRole selectById (Long id) ;
provider:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ... @SelectProvider (type = PrivilegeProvider.class, method = "selectById" )SysPrivilege selectById (Long id) ;... ... public String selectById (final Long id) { return new SQL() { { SELECT("id, privilege_name, privilege_url" ); FROM("sys_privilege" ); WHERE("id = #{id}" ); } }.toString(); } ...
MyBatis generator 的使用 通过 org.mybatis.generator
包可以在 xml 中进行设置来生成 model 对应的 sql 实现和 dao 接口,之后还要研究另一种实现方式,所以这里暂且不考虑内部实现,之后,我们可以使用对应 example 类来实现 sql ,这里也先不讨论。xml 主要的使用方式是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration > <context id ="MySqlContext" targetRuntime ="MyBatis3" defaultModelType ="flat" > <property name ="beginningDelimiter" value ="'" /> <property name ="endingDelimiter" value ="'" /> <property name ="javaFileEncoding" value ="UTF-8" /> <commentGenerator > <property name ="suppressDate" value ="true" /> <property name ="addRemarkComments" value ="true" /> </commentGenerator > <jdbcConnection driverClass ="com.mysql.jdbc.Driver" connectionURL ="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8" userId ="root" password ="czp" > </jdbcConnection > <javaModelGenerator targetPackage ="com.koon.generator.model" targetProject ="/Users/ronnie/work/GithubWorkspace/springboot-tourist/mybatis-started-to-master/chapter2/src/main/java" > <property name ="trimStrings" value ="true" /> </javaModelGenerator > <sqlMapGenerator targetPackage ="generator.mapper" targetProject ="/Users/ronnie/work/GithubWorkspace/springboot-tourist/mybatis-started-to-master/chapter2/src/main/resources" /> <javaClientGenerator type ="XMLMAPPER" targetPackage ="com.koon.generator.mapper" targetProject ="/Users/ronnie/work/GithubWorkspace/springboot-tourist/mybatis-started-to-master/chapter2/src/main/java" /> <table tableName ="country" > <generatedKey column ="id" sqlStatement ="MySQL" identity ="true" /> </table > </context > </generatorConfiguration >
一对多中 resultMap 指定 无论一对一、一对多中,我们都有可能要使用 resultMap ,其中可能是要在一个 xml 中使用另一个 xml 中的 resultMap ,那么,它的写法是先指向 mapper 接口文件,然后指向该接口文件对应 xml 中的 resultMap 的 id ,这也是 mapper 动态代理的一种应用:
1 2 3 4 5 6 7 8 9 10 11 <resultMap id ="userRoleListMap" type ="com.koon.model.SysUser" > <id column ="id" property ="id" /> <result column ="user_name" property ="userName" /> <result column ="user_password" property ="userPassword" /> <result column ="user_email" property ="userEmail" /> <result column ="user_info" property ="userInfo" /> <result column ="head_img" property ="headImg" jdbcType ="BLOB" /> <result column ="create_time" property ="createTime" jdbcType ="TIMESTAMP" /> <collection property ="roleList" columnPrefix ="role_" ofType ="com.koon.model.SysRole" resultMap ="com.koon.mapper.RoleMapper.rolePrivilegeListMap" /> </resultMap >
MyBatis 缓存 MyBatis 中存在一级缓存和二级缓存,一级缓存是指在一个 SqlSession 生命周期下相同键值存入一个 Map 对象中,可以通过参数修改来破除一级缓存:
1 2 3 4 <select id ="selectById" resultMap ="userMap" flushCache ="true" > select * from sys_user where id = #{id} </select >
二级缓存不止存在于 SqlSession 生命周期中,它存在于 SqlSessionFactory 生命周期中,在 mybatis-config.xml 中,可以对它进行设置,默认就是 true :
1 2 3 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
MyBaits 使用 SeiralizedCache
序列化缓存来实现可读写缓存类,并通过序列化和反序列化来保证通过缓存换取数据的时候,得到的是一个新的实例,所以我们常常对可序列化对象这样操作:
1 2 3 4 public class SysUser implements Serializable { private static final long serialVersionUID = -8280230645677496806L ; ... }
在 xml 文件和对应 mapper 接口下,我们都可以进行 cache 配置,二级缓存和命名空间绑定,所以通常每一个 mapper 都拥有自己的二级缓存。
1 2 3 4 5 6 @CacheNamespace ( eviction = FifoCache.class, flushInterval = 60000 , size = 512 , readWrite = true )
1 2 3 4 5 6 7 <cache eviction ="FIFO" flushInterval ="60000" size ="512" readOnly ="false" />
通过参照缓存,来确保我们在同一个二级缓存中,将同一个事务的对象设置为一个二级缓存,可以有效避免脏数据。
1 @CacheNamespaceRef (RoleMapper.class)
1 <cache-ref namespace ="com.koon.mapper.RoleMapper" />
后续将跟进通用 mapper 和分页插件