mybatis怎么实现两种数据库的兼容
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis怎么实现两种数据库的兼容相关的知识,希望对你有一定的参考价值。
参考技术A 方法:在系统运行过程中,针对不同数据库类型区分加载不同文件路径下的sqlmap配置。留给开发人员做的事依旧是快乐地编写原始数据库语句,而不再为多数据库兼容问题挠头。分析问题:
1、解决ibatis多数据库兼容的问题,实际上就是让ibatis可以自动选择不同数据库sqlmap配置文件。
2、打开ibatis源码工程并进行代码跟踪,最终定位到com.ibatis.sqlmap.engine.builder.xml.SqlMapConfigParser.java处,系统中运行的sqlmap节点均从这里统一转化加载的。
3、很明显SqlMapConfigParser源码中的addSqlMapNodelets负责解析“/sqlMapConfig/sqlMap”路径,即也就是sqlmap文件路径。
4、要实现“多数据库兼容”,这里改造下这个方法即可。
解决方法:
1、首选我们在节点新增一个“DBProductName”用于判断数据库种类。当然通过java.sql.DatabaseMetaData也可以获得,但这太依赖于jdbc驱动,还是手动配置保险。
2、重构SqlMapConfigParser的addSqlMapNodelets方法。
" 1)首选获得单个sqlmap文件的位置,如 sqlmap主目录\XXX.xml;
2)依据上面设置的DBProductName属性值获得数据库类型;
3)将sqlmap的文件路径重定位到运行数据库类型文件下。如 sqlmap主目录\mysql\XXX.xml,代表程序当前运行于msql数据库上,并加载系统mysql数据库对应的 sqlmap文件"
4、ok,现在ibatis支持“多数据库兼容”了(完整源码见附件)。
使用方法:
1、编译SqlMapConfigParser.java生成可运行文件SqlMapConfigParser.class(见附件),并将其覆盖添加到ibatis.jar中。
2、在系统sqlmap配置文件存放主目录如src\conf\mapping添加多数据库兼容支持的子目录,如src\conf\mapping\mysql,src\conf\mapping\oracle等
3、以实际项目需要编写多数据库sqlmap配置文件。注意:不同数据库类型需要确保具有相同的sqlmap配置文件名和sql节点名
4、在ibatis主配置文件(如sql-map-config.xml)中,指定当前程序运行数据库类型
MyBatis物理分页的代码实现
一.分页
MyBatis有两种分页方法:内存分页,也就是假分页,本质是查出所有的数据然后根据游标的方式,截取需要的记录,如果数据量大,执行效率低,可能造成内存溢出。物理分页,就是数据库本身提供了分页方式,如MySql的limit,执行效率高,不同数据库实现不同。
MyBatis Generator使用:MyBatis Generator使用示例
Spring集成MyBatis:Spring集成MyBatis持久层框架
二.MyBatis执行流程
MyBatis执行sql流程如下图,实现数据库的物理分页,需要通过拦截StatementHandler重写的sql语句。
三.分页实现
1.实现MyBatis的Interceptor接口,创建PageInterceptor类
@Intercepts({@Signature(type=StatementHandler.class, method = "prepare", args={Connection.class, Integer.class})}) public class PageInterceptor implements Interceptor { private String sqlRegEx = ".*Page"; public Object intercept(Invocation invocation) throws Throwable { RoutingStatementHandler handler = (RoutingStatementHandler)invocation.getTarget(); StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate"); BoundSql boundSql = delegate.getBoundSql(); MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement"); // 获取参数 Object parameterObject = boundSql.getParameterObject(); // 判断是否分页 if (mappedStatement.getId().matches(sqlRegEx)) { Page page = (Page) ((Map<?, ?>) parameterObject).get("page"); if (page != null) { Connection connection = (Connection) invocation.getArgs()[0]; // 获取mapper映射文件中对应的sql语句 String sql = boundSql.getSql(); // 给当前page参数设置总记录数 this.setPageParameter(mappedStatement, connection, boundSql, page); // 获取分页sql语句 String pageSql = this.getPageSql(page, sql); ReflectUtil.setFieldValue(boundSql, "sql", pageSql); } } return invocation.proceed(); } /** * 从数据库里查询总的记录数并计算总页数,回写进分页参数page * @param mappedStatement * @param connection * @param boundSql * @param page */ private void setPageParameter(MappedStatement mappedStatement, Connection connection, BoundSql boundSql, Page page) { // 获取mapper映射文件中对应的sql语句 String sql = boundSql.getSql(); // 获取计算总记录数的sql语句 String countSql = this.getCountSql(sql); // 获取BoundSql参数映射 List<ParameterMapping> parameterMappinglist = boundSql.getParameterMappings(); // 构造查询总量的BoundSql BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappinglist, boundSql.getParameterObject()); ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql); PreparedStatement pstmt = null; ResultSet rs = null; try { // 通过connection建立countSql对应的PreparedStatement对象 pstmt = connection.prepareStatement(countSql); parameterHandler.setParameters(pstmt); // 执行countSql语句 rs = pstmt.executeQuery(); if (rs.next()) { int totalRecord = rs.getInt(1); page.setTotalRecord(totalRecord); page.setTotalPage(totalRecord/page.getPageSize() + (totalRecord % page.getPageSize() == 0? 0: 1)); } } catch (SQLException e) { e.printStackTrace(); } } /** * 根据源sql语句获取对应的查询总记录数的sql语句 * @param sql * @return */ private String getCountSql(String sql) { int index = sql.indexOf("from"); return "select count(*) " + sql.substring(index); } /** * 获取MySql数据库的分页查询语句 * @param page * @param sql * @return */ private String getPageSql(Page<?> page, String sql) { StringBuffer sqlBuffer = new StringBuffer(sql); int offset = (page.getPageNum() - 1) * page.getPageSize(); sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize()); return sqlBuffer.toString(); } /** * 只处理StatementHandler类型 * @param o * @return */ public Object plugin(Object o) { if (o instanceof StatementHandler) { return Plugin.wrap(o, this); } else { return o; } } /** * 拦截器属性设定 * @param properties */ public void setProperties(Properties properties) { } public String getSqlRegEx() { return sqlRegEx; } public void setSqlRegEx(String sqlRegEx) { this.sqlRegEx = sqlRegEx; } }
2.保存页面的相关信息,创建Page类
public class Page<T> { private int pageNum = 1; private int pageSize = 5; private int totalRecord; private int totalPage; private List<T> results; public int getPageNum() { return pageNum; } public void setPageNum(int pageNum) { this.pageNum = pageNum; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public int getTotalRecord() { return totalRecord; } public void setTotalRecord(int totalRecord) { this.totalRecord = totalRecord; } public int getTotalPage() { return totalPage; } public void setTotalPage(int totalPage) { this.totalPage = totalPage; } public List<T> getResults() { return results; } public void setResults(List<T> results) { this.results = results; } }
3.通过反射获取对象的属性,创建ReflectUtil工具类,用于获取RoutingStatementHandler对象的私有属性delegate
public class ReflectUtil { public static Object getFieldValue(Object obj, String fieldName) { Object result = null; Field field = ReflectUtil.getField(obj, fieldName); if (null != field) { field.setAccessible(true); try { result = field.get(obj); } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } } return result; } private static Field getField(Object obj, String fieldName) { Field field = null; for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) { try { field = clazz.getDeclaredField(fieldName); break; } catch (NoSuchFieldException e) { } } return field; } public static void setFieldValue(Object obj, String fieldName, String fieldValue) { Field field = ReflectUtil.getField(obj, fieldName); if (null != field) { try { field.setAccessible(true); field.set(obj, fieldValue); } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } } } }
4.启用分页Interceptor,编辑applicationContext_database.xml
<!-- mybatis配置,mapper.xml文件扫描 --> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="configLocation" value="classpath:/mybatis/mybatis.xml"/> <property name="mapperLocations" value="classpath:/mybatis/mapper/*Mapper.xml"/> <property name="dataSource" ref="dataSource"/> <property name="plugins"> <array> <!-- 分页 --> <bean class="com.learn.spring.server.intercept.PageInterceptor"> <property name="properties"> <value> sqlRegEx = ".*Page" </value> </property> </bean> </array> </property> </bean>
四.调用示例
1.编辑UserServiceImpl类
@Service public class UserServiceImpl implements UserService { @Resource private UserDOMapper userDao; @Override public Page<UserDO> listByCondPage(Integer status, Page page) { Map<String, Object> param = new HashMap<>(); param.put("status", status); param.put("page", page); List<UserDO> userDOList = userDao.selectByCondPage(param); page.setResults(userDOList); return page; } }
2.编辑IndexController类,调用Service
@Controller @RequestMapping("/server") public class IndexController { @Resource private UserService userService; @ResponseBody @RequestMapping("/list") public Object list(Integer status, Integer pageNum, Integer pageSize) { Page<UserDO> userDOPage = new Page<>(); userDOPage.setPageNum(pageNum); userDOPage.setPageSize(pageSize); return userService.listByCondPage(status, userDOPage); } }
以上是关于mybatis怎么实现两种数据库的兼容的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot集成Mybatis实现多表查询的两种方式(基于xml)
Spring Boot 整合 MyBatis Plus实现多数据源的两种方式