MybatisConfig.java文件
1 import com.alibaba.druid.pool.DruidDataSource; 2 import com.xman.common.mybatis.SqlMonitorManager; 3 import org.apache.ibatis.plugin.Interceptor; 4 import org.apache.ibatis.session.SqlSessionFactory; 5 import org.mybatis.spring.SqlSessionFactoryBean; 6 import org.mybatis.spring.annotation.MapperScan; 7 import org.slf4j.Logger; 8 import org.slf4j.LoggerFactory; 9 import org.springframework.beans.factory.annotation.Value; 10 import org.springframework.context.annotation.Bean; 11 import org.springframework.context.annotation.Configuration; 12 import org.springframework.core.env.StandardEnvironment; 13 import org.springframework.core.io.ClassPathResource; 14 import org.springframework.core.io.Resource; 15 import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 16 import org.springframework.core.io.support.ResourcePatternResolver; 17 import org.springframework.jdbc.datasource.DataSourceTransactionManager; 18 import org.springframework.util.ClassUtils; 19 20 import javax.sql.DataSource; 21 import java.io.IOException; 22 import java.util.Properties; 23 24 /** 25 * Created by chai on 2017/10/11. 26 */ 27 @Configuration //用作配置信息 28 @MapperScan(basePackages = "com.xman.rainbow.dao") 29 public class MybatisConfig { 30 31 private static Logger logger = LoggerFactory.getLogger(MybatisConfig.class); 32 33 @Value("${datasource.driverClass}") 34 private String jdbcDriver; 35 36 @Value("${datasource.username}") 37 private String username; 38 39 @Value("${datasource.password}") 40 private String password; 41 42 @Value("${datasource.jdbcUrl}") 43 private String jdbcUrl; 44 45 @Value("${datasource.maxIdle}") 46 private String maxIdle; 47 48 //最小、最大 49 @Value("${datasource.minIdle}") 50 private String minIdle; 51 @Value("${datasource.maxActive}") 52 private int maxActive; 53 54 @Value("${datasource.maxWait}") 55 private String maxWait; //配置获取连接等待超时的时间 56 57 @Value("${datasource.validationQuery}") 58 private String validationQuery; 59 60 @Value("${datasource.testBorrow}") 61 private boolean testOnBorrow; 62 63 @Value("${datasource.testOnReturn}") 64 private boolean testOnReturn; 65 66 @Value("${datasource.testWhileIdle}") 67 private boolean testWhileIdle; 68 69 @Value("${datasource.timeBetweenEvictionRunsMills}") 70 private long timeBetweenEvictionRunsMills; //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 71 72 @Value("${datasource.minEvictableIdleTimeMillis}") 73 private long minEvictableTimeMills; //配置一个连接在池中最小生存的时间,单位是毫秒 74 75 @Bean //为Spring容器所管理 76 public DataSource dataSource() { 77 DruidDataSource dataSource = new DruidDataSource(); 78 dataSource.setDriverClassName(this.jdbcDriver); 79 dataSource.setUsername(this.username); 80 dataSource.setPassword(this.password); 81 dataSource.setUrl(this.jdbcUrl); 82 dataSource.setMaxActive(this.maxActive); 83 dataSource.setValidationQuery(this.validationQuery); 84 dataSource.setTestOnBorrow(this.testOnBorrow); 85 dataSource.setTestOnReturn(this.testOnReturn); 86 dataSource.setTestWhileIdle(this.testWhileIdle); 87 dataSource.setTimeBetweenConnectErrorMillis(this.timeBetweenEvictionRunsMills); 88 dataSource.setMinEvictableIdleTimeMillis(minEvictableTimeMills); 89 return dataSource; 90 } 91 92 @Bean 93 public SqlSessionFactory sqlSessionFactory() throws Exception { 94 logger.debug("start sqlSessionFactory"); 95 final SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean(); 96 // 设置datasource 97 sqlSessionFactory.setDataSource(dataSource()); 98 // 设置mybatis configuration 扫描路径 99 sqlSessionFactory.setConfigLocation(new ClassPathResource("mybatis-config.xml")); 100 sqlSessionFactory.setFailFast(true); 101 //自动扫描entity目录 102 sqlSessionFactory.setMapperLocations(getResource("mappers", "**/*.xml")); 103 SqlMonitorManager sqlMonitorManager = new SqlMonitorManager(); 104 Properties properties = new Properties(); 105 properties.setProperty("show_sql", "true"); 106 sqlMonitorManager.setProperties(properties); 107 108 PageInterceptor pageInterceptor = new PageInterceptor(); 109 Properties property = new Properties(); 110 properties.setProperty("databaseType", "mysql"); 111 pageInterceptor.setProperties(property); 112 sqlSessionFactory.setPlugins(new Interceptor[]{sqlMonitorManager, pageInterceptor}); 113 return sqlSessionFactory.getObject(); 114 } 115 116 private Resource[] getResource(String basePackage, String pattern) throws IOException { 117 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX 118 + ClassUtils.convertClassNameToResourcePath(new StandardEnvironment() 119 .resolveRequiredPlaceholders(basePackage)) + "/" + pattern; 120 Resource[] resources = new PathMatchingResourcePatternResolver().getResources(packageSearchPath); 121 return resources; 122 } 123 124 /** 125 * 配置事务管理组件 126 * @return 127 */ 128 @Bean 129 public DataSourceTransactionManager transactionManager() { 130 logger.debug("start transactionManager"); 131 return new DataSourceTransactionManager(dataSource()); 132 } 133 }
mybatis-config.xml

1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> 3 <configuration> 4 <settings> 5 <setting name="cacheEnabled" value="true"/> 6 <setting name="lazyLoadingEnabled" value="true"/> 7 <setting name="aggressiveLazyLoading" value="true"/> 8 <setting name="useGeneratedKeys" value="true"/> 9 <setting name="defaultExecutorType" value="SIMPLE"/> 10 <setting name="defaultStatementTimeout" value="10"/> 11 </settings> 12 </configuration>
PageInterceptor.java
1 import com.xman.rainbow.car.common.Page; 2 import com.xman.rainbow.car.common.Pagination; 3 import org.apache.ibatis.executor.parameter.ParameterHandler; 4 import org.apache.ibatis.executor.statement.RoutingStatementHandler; 5 import org.apache.ibatis.executor.statement.StatementHandler; 6 import org.apache.ibatis.mapping.BoundSql; 7 import org.apache.ibatis.mapping.MappedStatement; 8 import org.apache.ibatis.mapping.ParameterMapping; 9 import org.apache.ibatis.plugin.*; 10 import org.apache.ibatis.scripting.defaults.DefaultParameterHandler; 11 12 import java.lang.reflect.Field; 13 import java.sql.Connection; 14 import java.sql.PreparedStatement; 15 import java.sql.ResultSet; 16 import java.sql.SQLException; 17 import java.util.List; 18 import java.util.Map; 19 import java.util.Properties; 20 21 /** 22 * 分页拦截器,用于拦截需要进行分页查询的操作,然后对其进行分页处理。 利用拦截器实现Mybatis分页的原理: 23 * 要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前就会产生一个包含Sql语句的Statement对象,而且对应的Sql语句 24 * 是在Statement之前产生的,所以我们就可以在它生成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的 25 * prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用 26 * StatementHandler对象的prepare方法,即调用invocation.proceed()。 27 * 对于分页而言,在拦截器里面我们还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设 28 * 置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。 29 * 30 */ 31 @Intercepts({ @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) }) 32 public class PageInterceptor implements Interceptor { 33 34 private String databaseType;// 数据库类型,不同的数据库有不同的分页方法 35 36 /** 37 * 拦截后要执行的方法 38 */ 39 public Object intercept(Invocation invocation) throws Throwable { 40 // 对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler, 41 // BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler, 42 // SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是 43 // 处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个 44 // StatementHandler类型的delegate属性,RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、 45 // PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。 46 // 我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,又因为Mybatis只有在建立RoutingStatementHandler的时候 47 // 是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。 48 RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget(); 49 // 通过反射获取到当前RoutingStatementHandler对象的delegate属性 50 StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate"); 51 // 获取到当前StatementHandler的 boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了 52 // RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。 53 BoundSql boundSql = delegate.getBoundSql(); 54 // 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象 55 Object paramObj = boundSql.getParameterObject(); 56 57 // 判断参数里是否有page对象 58 Pagination page = null; 59 if (paramObj instanceof Pagination) { 60 page = (Pagination) paramObj; 61 } else if (paramObj instanceof Map) { 62 for (Object arg : ((Map) paramObj).values()) { 63 if (arg instanceof Page<?>) { 64 page = (Pagination) arg; 65 break; 66 } 67 } 68 } 69 70 // 这里我们简单的通过传入的参数含有Pagination对象就认定它是需要进行分页操作的。 71 if (page != null) { 72 // 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性 73 MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement"); 74 // 拦截到的prepare方法参数是一个Connection对象 75 Connection connection = (Connection) invocation.getArgs()[0]; 76 // 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句 77 String sql = boundSql.getSql(); 78 // 给当前的page参数对象设置总记录数 79 if (page.getTotalCount() < 0) { // 如果总数为负数表需要设置 80 this.setTotalRecord(paramObj, mappedStatement, connection, page); 81 } 82 // 获取分页Sql语句 83 String pageSql = this.getPageSql(page, sql); 84 // 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句 85 ReflectUtil.setFieldValue(boundSql, "sql", pageSql); 86 } 87 return invocation.proceed(); 88 } 89 90 /** 91 * 拦截器对应的封装原始对象的方法 92 */ 93 public Object plugin(Object target) { 94 return Plugin.wrap(target, this); 95 } 96 97 /** 98 * 设置注册拦截器时设定的属性 99 */ 100 public void setProperties(Properties properties) { 101 this.databaseType = properties.getProperty("databaseType"); 102 } 103 104 /** 105 * 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle 其它的数据库都 没有进行分页 106 * 107 * @param page 分页对象 108 * @param sql 原sql语句 109 * @return 110 */ 111 private String getPageSql(Pagination page, String sql) { 112 StringBuffer sqlBuffer = new StringBuffer(sql); 113 if ("mysql".equalsIgnoreCase(databaseType)) { 114 return getMysqlPageSql(page, sqlBuffer); 115 } else if ("oracle".equalsIgnoreCase(databaseType)) { 116 return getOraclePageSql(page, sqlBuffer); 117 } 118 return getMysqlPageSql(page, sqlBuffer); 119 } 120 121 /** 122 * 获取Mysql数据库的分页查询语句 123 * 124 * @param page 分页对象 125 * @param sqlBuffer 包含原sql语句的StringBuffer对象 126 * @return Mysql数据库分页语句 127 */ 128 private String getMysqlPageSql(Pagination page, StringBuffer sqlBuffer) { 129 // 计算第一条记录的位置,Mysql中记录的位置是从0开始的。 130 int offset = (page.getPageNo() - 1) * page.getPageCount(); 131 sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageCount()); 132 return sqlBuffer.toString(); 133 } 134 135 /** 136 * 获取Oracle数据库的分页查询语句 137 * 138 * @param page 分页对象 139 * @param sqlBuffer 包含原sql语句的StringBuffer对象 140 * @return Oracle数据库的分页查询语句 141 */ 142 private String getOraclePageSql(Pagination page, StringBuffer sqlBuffer) { 143 // 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的 144 int offset = (page.getPageNo() - 1) * page.getPageCount() + 1; 145 sqlBuffer.insert(0, "select u.*, rownum _rownum from (").append(") u where rownum < ") 146 .append(offset + page.getPageCount()); 147 sqlBuffer.insert(0, "select * from (").append(") where _rownum >= ").append(offset); 148 // 上面的Sql语句拼接之后大概是这个样子: 149 // select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16 150 return sqlBuffer.toString(); 151 } 152 153 /** 154 * 给当前的参数对象page设置总记录数 155 * 156 * @param obj Mapper映射语句对应的参数对象 157 * @param mappedStatement Mapper映射语句 158 * @param connection 当前的数据库连接 159 */ 160 private void setTotalRecord(Object obj, MappedStatement mappedStatement, Connection connection, Pagination page) { 161 // 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。 162 // delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。 163 BoundSql boundSql = mappedStatement.getBoundSql(obj); 164 // 获取到我们自己写在Mapper映射语句中对应的Sql语句 165 String sql = boundSql.getSql(); 166 // 通过查询Sql语句获取到对应的计算总记录数的sql语句 167 String countSql = this.getCountSql(sql); 168 // 通过BoundSql获取对应的参数映射 169 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); 170 // 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。 171 BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, obj); 172 // 在原boundSQL中存在additionalParameters等参数,new出来的sql可能没有这些参数,会造成生成sql是报错,所以设置进来 173 // 还有一种方法即就用原来的BoundSql, 改掉里面的sql, 用完后再改回来即可 174 ReflectUtil.setFieldValue(countBoundSql, "additionalParameters", ReflectUtil.getFieldValue(boundSql, "additionalParameters")); 175 ReflectUtil.setFieldValue(countBoundSql, "metaParameters", ReflectUtil.getFieldValue(boundSql, "metaParameters")); 176 177 // 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象 178 ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, obj, countBoundSql); 179 // 通过connection建立一个countSql对应的PreparedStatement对象。 180 PreparedStatement pstmt = null; 181 ResultSet rs = null; 182 try { 183 pstmt = connection.prepareStatement(countSql); 184 // 通过parameterHandler给PreparedStatement对象设置参数 185 parameterHandler.setParameters(pstmt); 186 // 之后就是执行获取总记录数的Sql语句和获取结果了。 187 rs = pstmt.executeQuery(); 188 if (rs.next()) { 189 int totalRecord = rs.getInt(1); 190 // 给当前的参数page对象设置总记录数 191 page.setTotalCount(totalRecord); 192 } 193 } catch (SQLException e) { 194 e.printStackTrace(); 195 } finally { 196 try { 197 if (rs != null) 198 rs.close(); 199 if (pstmt != null) 200 pstmt.close(); 201 } catch (SQLException e) { 202 e.printStackTrace(); 203 } 204 } 205 } 206 207 /** 208 * 根据原Sql语句获取对应的查询总记录数的Sql语句 209 * 210 * @param sql 211 * @return 212 */ 213 private String getCountSql(String sql) { 214 return "select count(1) from (" + sql + ") _tmp"; 215 } 216 217 /** 218 * 利用反射进行操作的一个工具类 219 * 220 */ 221 private static class ReflectUtil { 222 /** 223 * 利用反射获取指定对象的指定属性 224 * 225 * @param obj 目标对象 226 * @param fieldName 目标属性 227 * @return 目标属性的值 228 */ 229 public static Object getFieldValue(Object obj, String fieldName) { 230 Object result = null; 231 Field field = ReflectUtil.getField(obj, fieldName); 232 if (field != null) { 233 field.setAccessible(true); 234 try { 235 result = field.get(obj); 236 } catch (IllegalArgumentException e) { 237 // TODO Auto-generated catch block 238 e.printStackTrace(); 239 } catch (IllegalAccessException e) { 240 // TODO Auto-generated catch block 241 e.printStackTrace(); 242 } 243 } 244 return result; 245 } 246 247 /** 248 * 利用反射获取指定对象里面的指定属性 249 * 250 * @param obj 目标对象 251 * @param fieldName 目标属性 252 * @return 目标字段 253 */ 254 private static Field getField(Object obj, String fieldName) { 255 Field field = null; 256 for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) { 257 try { 258 field = clazz.getDeclaredField(fieldName); 259 break; 260 } catch (NoSuchFieldException e) { 261 // 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。 262 } 263 } 264 return field; 265 } 266 267 /** 268 * 利用反射设置指定对象的指定属性为指定的值 269 * 270 * @param obj 目标对象 271 * @param fieldName 目标属性 272 * @param fieldValue 目标值 273 */ 274 /* public static void setFieldValue(Object obj, String fieldName, String fieldValue) { 275 Field field = ReflectUtil.getField(obj, fieldName); 276 if (field != null) { 277 try { 278 field.setAccessible(true); 279 field.set(obj, fieldValue); 280 } catch (IllegalArgumentException e) { 281 // TODO Auto-generated catch block 282 e.printStackTrace(); 283 } catch (IllegalAccessException e) { 284 // TODO Auto-generated catch block 285 e.printStackTrace(); 286 } 287 } 288 }*/ 289 /** 290 * 利用反射设置指定对象的指定属性为指定的值 291 * 292 * @param obj 目标对象 293 * @param fieldName 目标属性 294 * @param fieldValue 目标值 295 */ 296 public static void setFieldValue(Object obj, String fieldName, Object fieldValue) { 297 Field field = ReflectUtil.getField(obj, fieldName); 298 if (field != null) { 299 try { 300 field.setAccessible(true); 301 field.set(obj, fieldValue); 302 } catch (IllegalArgumentException e) { 303 // TODO Auto-generated catch block 304 e.printStackTrace(); 305 } catch (IllegalAccessException e) { 306 // TODO Auto-generated catch block 307 e.printStackTrace(); 308 } 309 } 310 } 311 } 312 313 }
application.properties文件
1 #### 测试环境 2 3 # datasource 4 # 驱动配置信息 5 datasource.driverClass=com.mysql.jdbc.Driver 6 datasource.username=root 7 datasource.password=123456 8 datasource.jdbcUrl=localhost 9 #连接池的配置信息 10 datasource.maxIdle=20 11 datasource.minIdle=1 12 datasource.maxWait=60000 13 datasource.validationQuery=: /* ping */ select 1 14 datasource.maxActive=3 15 datasource.testBorrow=false 16 datasource.testOnReturn=false 17 datasource.testWhileIdle=true 18 datasource.timeBetweenEvictionRunsMills=60000 19 datasource.minEvictableIdleTimeMillis=60000