今天来记录一下对Mybatis的扩展,版本是3.3.0,是和Spring集成使用,mybatis-spring集成包的版本是1.2.3,如果使用maven,如下配置:
<properties>元素下添加
1 <mybatis.version>3.3.0</mybatis.version> 2 <mybatis.spring.version>1.2.3</mybatis.spring.version>
<dependencies>元素下添加
1 <dependency> 2 <groupId>org.mybatis</groupId> 3 <artifactId>mybatis</artifactId> 4 <version>${mybatis.version}</version> 5 </dependency> 6 <dependency> 7 <groupId>org.mybatis</groupId> 8 <artifactId>mybatis-spring</artifactId> 9 <version>${mybatis.spring.version}</version> 10 </dependency>
mybatis-spring的集成配置如下:
1 <bean id="DialectDatabaseIdProvider" class="com.forms.beneform4j.core.dao.mybatis.provider.DialectDatabaseIdProvider"/> 2 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" p:sqlSessionFactoryBeanName="sqlSessionFactory" p:basePackage="com.forms" 3 p:annotationClass="org.springframework.stereotype.Repository"/> 4 <bean id="sqlSessionFactory" p:dataSource-ref="dataSource" class="com.forms.beneform4j.core.dao.mybatis.SqlSessionFactoryBeanForSpring" 5 p:configLocation="classpath:mybatis/mybatis-config.xml" p:databaseIdProvider-ref="DialectDatabaseIdProvider"> 6 <property name="mapperLocations"> 7 <array> 8 <value>classpath*:sql-mapper/ds1/*.xml</value> 9 <value>classpath*:com/forms/beneform4j/**/*.ds1.xml</value> 10 </array> 11 </property> 12 </bean>
这里的p:databaseIdProvider-ref="DialectDatabaseIdProvider"算是我们的第一个扩展:
扩展1:添加自定义的数据库ID提供者
添加这个扩展的目的,是为了能够根据不同的数据库写不同的sql-mapper配置,比如如下的配置,就可兼容不同数据库:
1 <choose> 2 <when test="_databaseId == 'oracle'"> 3 </when> 4 <when test="_databaseId == 'db2'"> 5 </when> 6 <when test="_databaseId == 'mysql'"> 7 </when> 8 <when test="_databaseId == 'oracle'"> 9 </when> 10 <when test="_databaseId == 'ase'"> 11 </when> 12 <when test="_databaseId == 'iq'"> 13 </when> 14 <when test="_databaseId == 'h2'"> 15 </when> 16 </choose>
那这里的oracle、db2字符串是怎么确定的呢?这就是名称为DialectDatabaseIdProvider的bean所做的事情了,基本上和Mybatis原生提供的VendorDatabaseIdProvider类似,根据数据源对象DataSource获取DatabaseMetaData,继而调用getDatabaseProductName获取产品名称,然后根据配置好的关键字确定数据库类型。可能读者会觉得这个扩展没多大必要,直接使用VendorDatabaseIdProvider就好了,的确如此,除了自己写的类有更大的控制权之外,没有增加多少功能。但DialectDatabaseIdProvider的实现是从数据库方言角度来做的,从这里可以引出第二个扩展:
扩展2:实现物理分页
Mybatis的分页查询是逻辑分页,这对于大多数应用是不适合的,于是需要实现物理分页。
主要的实现步骤:
1、添加数据库方言接口
1 public interface IDialect { 2 3 /** 4 * 数据库类型 5 */ 6 enum DBType { 7 Oracle, DB2, H2, MySql, ASE, IQ 8 } 9 10 /** 11 * 获取数据库类型 12 * @return 数据库类型枚举常量 13 */ 14 public DBType getType(); 15 16 /** 17 * 获取可能的数据库驱动类名称 18 * @return 驱动类名数组 19 */ 20 public String[] getDriverClassNames(); 21 22 /** 23 * 生成计算总记录数的SQL 24 * @param sql 原始SQL 25 * @return 计算总记录数的SQL 26 */ 27 public String getTotalSql(String sql); 28 29 /** 30 * 获取查询指定范围记录的SQL 31 * @param sql 原始SQL 32 * @param offset 返回的开始记录索引 33 * @param limit 查询的数据大小 34 * @return 查询第(offset, offset + limit]条记录的SQL,索引从1开始 35 */ 36 public String getScopeSql(String sql, long offset, int limit); 37 }
不同的数据库具体怎么实现就不贴了;
2、添加mybatis分页和物理分页的适配类
1 public class PageAdapter extends RowBounds{ 2 3 }
在调用Mybatis的分页查询时,使用该适配类参数,例如:
1 public <E> List<E> selectList(String sqlId, Object parameter, IPage page) { 2 RowBounds adapter = new PageAdapter(page); 3 return sqlSession.selectList(sqlId, parameter, adapter); 4 }
这里IPage封装了分页对象,可以获取当前页数、分页大小等等。
3、编写Mybatis插件,拦截sql执行,如果包含PageAdapter参数,则做分页逻辑处理
(1)获取数据库方言接口的具体实现类,这里可以根据Connection对象识别、也可以使用ThreadLocal对象,还可以在适配类中添加方言对象传进来
(2)如果需要计算总记录条数(分页对象中不包含总记录数),根据数据库方言,获取计算总记录条数的sql,然后执行,并将总计录条数反写会分页对象
(3)根据数据库方言和分页对象,获取需要从数据库查询的当前页数据的sql,然后调用拦截器的执行方法继续之前执行
4、将mybatis插件配置到spring配置文件或mybatis配置文件中,启用该插件即可。
说到Mybatis插件,实际上并不只是做了分页处理一件事,还可以格式化sql、进行sql拦截处理等等,这里就包括我们的第三个扩展:
扩展3:SQL拦截1——语句拦截
主要的实现步骤:
1、添加SQL字符串拦截接口
1 public interface ISqlInterceptor { 2 3 /** 4 * 执行SQL拦截 5 * @param jndi 数据源 6 * @param src 原SQL 7 * @param context 上下文环境 8 * @param root 根对象 9 * @return 拦截后的SQL 10 */ 11 public String intercept(IJndi jndi, String src, Map<String, Object> context, Object root); 12 }
这里的IJndi对象是数据源对象的一个抽象,可以获取相应的数据库方言对象、和数据源对应的特定属性等
1 public interface IJndi{ 2 3 /** 4 * 获取数据源名称 5 * <p> 6 * 默认为spring配置中的beanId 7 * </p> 8 * @return 数据源名称 9 */ 10 public String getName(); 11 12 /** 13 * 是否默认数据源 14 * <p> 15 * 默认情况下,如果spring配置中的beanId等于dataSource(不区分大小写)则设置为默认数据源,如果不存在dataSource,则设置第一个数据源为默认数据源 16 * </p> 17 * @return 是否默认数据源 18 */ 19 public boolean isDefault(); 20 21 /** 22 * 获取数据库方言 23 * <p> 24 * 根据数据库产品名称自动判断哪个数据库方言 25 * </p> 26 * @return 和数据源对应的数据库方言 27 */ 28 public IDialect getDialect(); 29 30 /** 31 * 获取数据源 32 * @return 对应的数据源 33 */ 34 public DataSource getDataSource(); 35 36 /** 37 * 获取属性 38 * @return 39 */ 40 public Properties getProperties(); 41 }
2、在平台配置中,添加sql拦截的配置,可以配置一系列的拦截
3、在Mybatis插件中,根据Mybatis执行上下文找到IJndi对象、参数对象、拦截前的sql,调用平台配置中的sql拦截,并将返回值反写会mybatis上下文中
可能有人会问,这种语句拦截有什么作用呢?
其实我们就是利用这种机制,来实现不同应用系统使用不同的表前缀,比如平台中有一个BF_USER表,A系统建表的时候统一添加A_前缀,B系统建表的时候统一添加前缀B_,那么,在平台中怎么知道访问的具体表呢?我们的做法是,在平台中添加一个平台表前缀的配置,然后实现一个SQL拦截,将符合特别语法{{BF}}的替换为Beneform4jConfig.getBeneform4jTablePrefix() + "BF",这种SQL语句的拦截,有点类似于使用${}语法。
扩展4:SQL拦截2——参数拦截
如果说SQL语句拦截是使用${}语法,那么参数拦截则是#{}语法了。
主要实现步骤:
1、添加参数拦截接口
1 public interface IStatementParameterResolver { 2 3 /** 4 * 是否可以解析表达式 5 * @param jndi 数据源对象 6 * @param expression 表达式 7 * @return 如果可以解析,返回true,否则返回false 8 */ 9 public boolean isSupport(IJndi jndi, String expression); 10 11 /** 12 * 执行参数解析 13 * @param jndi 数据源对象 14 * @param parameterObject 参数对象 15 * @param expression 表达式 16 * @return 解析后的值 17 */ 18 public Object resolver(IJndi jndi, Object parameterObject, String expression); 19 }
这里有两个方法,第一个方法判断当前的拦截器实现类是否支持#{}内部的表达式,如果支持,就调用第二个方法处理参数。这种先判断是否支持,然后在支持的情况下再继续处理的类责任链模式在Spring MVC中用的也相当普通。
2、编写Mybatis原生接口ParameterHandler的实现类,在其中调用参数拦截接口来实现不同参数的解析
3、在Mybatis插件中,替换ParameterHandler接口的实现类。
在新平台中,利用参数拦截主要实现了一种效果,就是在mybatis的sql-mapper文件中直接执行SpEL表达式或OGNL表达式。例如:
1 <select id="selectList"> 2 SELECT * FROM BF_PARAM_ENUM_DEF 3 <where> 4 <if test="null != paramCode and '' != paramCode"> 5 and PARAM_CODE = #{@bean.getName(), jdbctType=VARCHAR} 6 </if> 7 </where> 8 </select>
这里的bean是Spring容器管理的bean,getBean就是调用其getBean方法了。
今天记录到这里,下周继续。