概述
MyBatis-15MyBatis动态SQL之【bind】 bind标签并不能解决更换数据库带来的所有问题。 那么还有没其他的方式来支持不同的数据库呢 ?
答案是 使用if标签以及由MyBatis提供的databaseIdProvider数据库厂商标识配置
MyBatis可以根据不同的数据库厂商执行不同的SQL,这种多厂商的支持是基于映射语句中的databaseId属性。 MyBatis会加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有语句。 如果同时找到了带有databaseId和不带databaseId的相同语句,则不带databaseId的将被舍弃。
配置
1.MyBatis全局配置文件增加
为了支持多厂商数据库,需要在MyBatis全局配置文件中加入databaseIdProvider配置 <databaseIdProvider type="DB_VENDOR"/>
也可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider 并在 mybatis-config.xml 中注册来构建自己的 DatabaseIdProvider
DB_VENDOR会通过DatabaseMetaData#getDatabaseProductName()返回的字符串进行设置, 通常情况下这个字符串比较长而且相同产品的不同版本会返回不同的值,通常会设置属性别名使其变短。
<!-- 多数据库支持 --> <databaseIdProvider type="DB_VENDOR"> <property name ="SQL Server" value="sqlserver"/> <property name ="DB2" value ="db2"/> <property name ="Oracle" value ="oracle"/> <property name ="MySQL" value ="mysql"/> <property name ="PostgreSQL" value ="postgresql"/> <property name ="Derby" value ="derby"/> <property name ="HSQL" value ="hsqldb"/> <property name ="H2" value ="h2"/> </databaseIdProvider>
上述列举了常见的数据库产品名称,在有property配置时,databaseId将被设置为第一个能匹配数据库产品名称的属性键对应的值,如果没有匹配则置为null .
DB_VENDOR的匹配策略为DatabaseMetaData#getDatabaseProductName()返回的字符串包含property中name部分的值即可匹配。
数据库产品名一般由选择的当前数据库的JDBC驱动所决定,只要找到对应数据库DatabaseMetaData的实现类,一般在getDatabaseProductName()方法中就可以直接找到该值。
任何情况下都可以通过调用DatabaseMetaData#getDatabaseProductName()来获取具体的值
完整的mybatis-config.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> <!-- 引入外部属性文件 ,必须放在第一位--> <properties resource="db.properties"/> <settings> <!-- 通过logImpl属性指定使用LOG4J输出日志,mybatis默认使用log4j作为输出日志信息。 --> <setting name="logImpl" value="LOG4J" /> <!-- 通过配置这个属性为true可以自动将下画线方式命名的数据库列映射到java对象驼峰式命名属性中 <setting name="mapUnderscoreToCamelCase" value="true"/> --> </settings> <!-- typeAliases元素下配置了一个包的别名,通常确定一个类的时候需要使用全限定名, 比如 com.artisan.mybatis.simple.mapper.model.Country --> <typeAliases> <package name="com.artisan.mybatis.simple.model" /> </typeAliases> <!-- 和spring整合后 environments配置将废除--> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理--> <transactionManager type="JDBC"/> <!-- 数据库连接池--> <dataSource type="UNPOOLED"> <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> <!-- 多数据库支持 --> <databaseIdProvider type="DB_VENDOR" > <property name ="SQL Server" value="sqlserver"/> <property name ="DB2" value ="db2"/> <property name ="Oracle" value ="oracle"/> <property name ="MySQL" value ="mysql"/> <property name ="PostgreSQL" value ="postgresql"/> <property name ="Derby" value ="derby"/> <property name ="HSQL" value ="hsqldb"/> <property name ="H2" value ="h2"/> </databaseIdProvider> <mappers> <!-- 配置具体的mapper --> <mapper resource="com/artisan/mybatis/simple/mapper/CountryMapper.xml" /> <!-- 逐一配置,比较繁琐,容易遗漏,接口方式不推荐 <mapper resource="com/artisan/mybatis/xml/mapper/UserMapper.xml"/> <mapper resource="com/artisan/mybatis/xml/mapper/UserRoleMapper.xml"/> <mapper resource="com/artisan/mybatis/xml/mapper/RoleMapper.xml"/> <mapper resource="com/artisan/mybatis/xml/mapper/PrivilegeMapper.xml"/> <mapper resource="com/artisan/mybatis/xml/mapper/RolePrivilegeMapper.xml"/> --> <!-- 推荐:通过包的方式配置,mybatis会先查找对应包下的所有的接口 --> <package name="com.artisan.mybatis.xml.mapper"/> </mappers> </configuration>
注意databaseIdProvider节点的位置。
2.映射文件中的标签调整包含databaseId属性
除了增加上述配置之外,映射文件也需要调整,关键在于下面几个映射文件的标签中含有的databaseId属性
- select
- insert
- update
- delete
- selectKey
- sql
示例
举个简单的例子,查询当前时间
我们知道 mysql中的语句为
select now() from dual
oracle中为
select sysdate from dual
增加个查询当前时间的接口
结合mybatis全局配置文件中的 mappers-package节点,在com.artisan.mybatis.xml.mapper包中增加接口
MultiDBMapper.java
package com.artisan.mybatis.xml.mapper; public interface MultiDBMapper { String getSysTime(); }
编写映射文件
MultiDBMapper.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接口和XML文件关联的时候, namespace的值就需要配置成接口的全限定名称 --> <mapper namespace="com.artisan.mybatis.xml.mapper.MultiDBMapper"> <select id="getSysTime" resultType="String" databaseId="mysql"> select now() from dual </select> <select id="getSysTime" resultType="String" databaseId="oracle"> select 'oralce-'||to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual </select> </mapper>
单元测试
package com.artisan.mybatis.xml.mapper; import java.io.InputStream; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.util.Properties; import org.apache.ibatis.session.SqlSession; import org.junit.Test; public class MultiDBMapperTest extends BaseMapperTest { public String getDatabaseProductName() { String productName = null; try { String dbfile = "db.properties"; InputStream in = ClassLoader.getSystemResourceAsStream(dbfile); Properties p = new Properties(); p.load(in); Class.forName(p.getProperty("jdbc.driver")); String url = p.getProperty("jdbc.url"); String user = p.getProperty("jdbc.username"); String pass = p.getProperty("jdbc.password"); Connection con = DriverManager.getConnection(url, user, pass); DatabaseMetaData dbmd = con.getMetaData(); productName = dbmd.getDatabaseProductName(); System.out.println("数据库名称是:" + productName); } catch (Exception e) { e.printStackTrace(); } return productName; } @Test public void getSysTimeTest() { // 获取数据库名称 getDatabaseProductName(); // 获取SqlSession SqlSession sqlSession = getSqlSession(); // 获取MultiDBMapper MultiDBMapper multiDBMapper = sqlSession.getMapper(MultiDBMapper.class); // 调用接口方法 String sysTime = multiDBMapper.getSysTime(); System.out.println("当前时间:" + sysTime); sqlSession.close(); } }
日志
2018-04-27 16:05:57,730 INFO [main] (BaseMapperTest.java:26) - sessionFactory bulit successfully 2018-04-27 16:05:57,730 INFO [main] (BaseMapperTest.java:29) - reader close successfully 数据库名称是:MySQL 2018-04-27 16:05:57,819 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: select now() from dual 2018-04-27 16:05:57,929 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: 2018-04-27 16:05:57,959 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: now() 2018-04-27 16:05:57,959 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 2018-04-27 16:05:57.0 2018-04-27 16:05:57,969 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 1 当前时间:2018-04-27 16:05:57.0
当基于不同的数据库运行时,MyBatis会根据配置找到合适的SQL去执行。
注意事项
我们在上面演示了基于databaseId的基本用法,实际在工作中,大部分的SQL还是相同的,没有必要写成2个。这样会导致大量重复的SQL。
数据库的更换可能只会引起某个SQL语句的部分不同, 可以使用if标签配合默认的上下文中的_databaseId参数去实现。
我们用前几篇博文的例子来演示下
改造前
<select id="selectSysUsersAdvancedWithWhere" resultType="com.artisan.mybatis.xml.domain.SysUser"> SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a <where> <if test="userName != null and userName != '' "> and user_name like concat('%',#{userName},'%') </if> <if test="userEmail != null and userEmail != '' "> and user_email = #{userEmail} </if> </where> </select>
改造后
<!-- 多数据库的支持 BEGIN --> <select id="selectSysUsersAdvancedMulitDB" resultType="com.artisan.mybatis.xml.domain.SysUser"> SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a <where> <if test="userName != null and userName != '' "> <if test="_databaseId == 'mysql' "> and user_name like concat('%',#{userName},'%') </if> <if test="_databaseId == 'oracle' "> and user_name like '%'||#{username}||'%' </if> </if> <if test="userEmail != null and userEmail != '' "> and user_email = #{userEmail} </if> </where> </select> <!-- 多数据库的支持 END -->
增加个接口方法,方便区分测试
/** * * * @Title: selectSysUsersAdvancedMulitDB * * @Description: selectSysUsersAdvancedMulitDB * * @param sysUser * @return * * @return: List<SysUser> */ List<SysUser> selectSysUsersAdvancedMulitDB(SysUser sysUser);
单元测试
@Test public void selectSysUsersAdvancedMulitDBTest() { logger.info("selectSysUsersAdvancedMulitDBTest"); // 获取SqlSession SqlSession sqlSession = getSqlSession(); List<SysUser> userList = null; try { // 获取UserMapper接口 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); logger.info("===========1.当用户只输入用户名时,需要根据用户名模糊查询==========="); // 模拟前台传参 1.当用户只输入用户名时,需要根据用户名模糊查询 SysUser sysUser = new SysUser(); sysUser.setUserName("ad"); // 调用selectSysUserByAdvancedCondition,根据查询条件查询用户 userList = userMapper.selectSysUsersAdvancedMulitDB(sysUser); // 根据数据库sys_user表中的记录,可以匹配到admin, 期望userList不为空 Assert.assertNotNull(userList); // 根据查询条件,期望只有1条数据 Assert.assertTrue(userList.size() == 1); logger.info("userList:" + userList); // 为了测试 匹配多条记录的情况,我们将id=1001这条数据的userName 由test 改为artisan sysUser.setUserName("i"); // 调用selectSysUserByAdvancedCondition,根据查询条件查询用户 userList = userMapper.selectSysUsersAdvancedMulitDB(sysUser); // 根据数据库sys_user表中的记录,可以匹配到admin和artisan, 期望userList不为空 Assert.assertNotNull(userList); // 根据查询条件,期望只有2条数据 Assert.assertTrue(userList.size() == 2); logger.info("userList:" + userList); logger.info("===========2.当用户只输入邮箱使,根据邮箱进行完全匹配==========="); // 模拟前台传参 2.当用户只输入邮箱使,根据邮箱进行完全匹配 sysUser.setUserEmail("admin@artisan.com"); userList = userMapper.selectSysUsersAdvanced(sysUser); Assert.assertNotNull(userList); Assert.assertTrue(userList.size() == 1); logger.info(userList); sysUser.setUserEmail("1admin@artisan.com"); userList = userMapper.selectSysUsersAdvancedMulitDB(sysUser); Assert.assertTrue(userList.size() == 0); logger.info("===========3.当用户同时输入用户名和密码时,用这两个条件查询匹配的用户==========="); // 模拟组合查询条件,存在记录的情况 sysUser.setUserName("i"); sysUser.setUserEmail("admin@artisan.com"); userList = userMapper.selectSysUsersAdvancedMulitDB(sysUser); Assert.assertNotNull(userList); Assert.assertEquals("admin@artisan.com", sysUser.getUserEmail()); Assert.assertTrue(userList.size() == 1); logger.info(userList); logger.info("===========4.当用户同时输入无法匹配的用户名和密码==========="); // 模拟组合查询条件,不存在记录的情况 sysUser.setUserName("x"); sysUser.setUserEmail("admin@artisan.com"); userList = userMapper.selectSysUsersAdvancedMulitDB(sysUser); Assert.assertTrue(userList.size() == 0); logger.info(userList); } catch (Exception e) { e.printStackTrace(); } finally { sqlSession.close(); logger.info("sqlSession close successfully "); } }
日志
2018-04-27 16:22:51,822 INFO [main] (BaseMapperTest.java:26) - sessionFactory bulit successfully 2018-04-27 16:22:51,826 INFO [main] (BaseMapperTest.java:29) - reader close successfully 2018-04-27 16:22:51,827 INFO [main] (UserMapperTest.java:934) - selectSysUsersAdvancedMulitDBTest 2018-04-27 16:22:51,857 INFO [main] (UserMapperTest.java:943) - ===========1.当用户只输入用户名时,需要根据用户名模糊查询=========== 2018-04-27 16:22:51,987 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE user_name like concat('%',?,'%') 2018-04-27 16:22:52,086 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: ad(String) 2018-04-27 16:22:52,157 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime 2018-04-27 16:22:52,157 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, admin, 123456, admin@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-27 16:22:52,167 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 1 2018-04-27 16:22:52,167 INFO [main] (UserMapperTest.java:953) - userList:[SysUser [id=1, userName=admin, userPassword=123456, userEmail=admin@artisan.com, userInfo=管理员用户, headImg=[18, 49, 35, 18, 48], createTime=Fri Apr 13 21:12:47 BOT 2018]] 2018-04-27 16:22:52,177 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE user_name like concat('%',?,'%') 2018-04-27 16:22:52,177 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: i(String) 2018-04-27 16:22:52,177 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime 2018-04-27 16:22:52,177 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, admin, 123456, admin@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-27 16:22:52,177 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1001, artisan, 123456, test@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-27 16:22:52,177 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 2 2018-04-27 16:22:52,187 INFO [main] (UserMapperTest.java:964) - userList:[SysUser [id=1, userName=admin, userPassword=123456, userEmail=admin@artisan.com, userInfo=管理员用户, headImg=[18, 49, 35, 18, 48], createTime=Fri Apr 13 21:12:47 BOT 2018], SysUser [id=1001, userName=artisan, userPassword=123456, userEmail=test@artisan.com, userInfo=测试用户, headImg=[18, 49, 35, 18, 48], createTime=Fri Apr 13 21:12:47 BOT 2018]] 2018-04-27 16:22:52,187 INFO [main] (UserMapperTest.java:966) - ===========2.当用户只输入邮箱使,根据邮箱进行完全匹配=========== 2018-04-27 16:22:52,187 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE 1=1 and user_name like concat('%',?,'%') and user_email = ? 2018-04-27 16:22:52,187 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: i(String), admin@artisan.com(String) 2018-04-27 16:22:52,187 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime 2018-04-27 16:22:52,187 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, admin, 123456, admin@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-27 16:22:52,187 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 1 2018-04-27 16:22:52,187 INFO [main] (UserMapperTest.java:972) - [SysUser [id=1, userName=admin, userPassword=123456, userEmail=admin@artisan.com, userInfo=管理员用户, headImg=[18, 49, 35, 18, 48], createTime=Fri Apr 13 21:12:47 BOT 2018]] 2018-04-27 16:22:52,187 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE user_name like concat('%',?,'%') and user_email = ? 2018-04-27 16:22:52,197 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: i(String), 1admin@artisan.com(String) 2018-04-27 16:22:52,197 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 0 2018-04-27 16:22:52,197 INFO [main] (UserMapperTest.java:978) - ===========3.当用户同时输入用户名和密码时,用这两个条件查询匹配的用户=========== 2018-04-27 16:22:52,197 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE user_name like concat('%',?,'%') and user_email = ? 2018-04-27 16:22:52,197 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: i(String), admin@artisan.com(String) 2018-04-27 16:22:52,197 TRACE [main] (BaseJdbcLogger.java:151) - <== Columns: id, userName, userPassword, userEmail, userInfo, headImg, createTime 2018-04-27 16:22:52,207 TRACE [main] (BaseJdbcLogger.java:151) - <== Row: 1, admin, 123456, admin@artisan.com, <<BLOB>>, <<BLOB>>, 2018-04-13 21:12:47.0 2018-04-27 16:22:52,207 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 1 2018-04-27 16:22:52,207 INFO [main] (UserMapperTest.java:987) - [SysUser [id=1, userName=admin, userPassword=123456, userEmail=admin@artisan.com, userInfo=管理员用户, headImg=[18, 49, 35, 18, 48], createTime=Fri Apr 13 21:12:47 BOT 2018]] 2018-04-27 16:22:52,207 INFO [main] (UserMapperTest.java:989) - ===========4.当用户同时输入无法匹配的用户名和密码=========== 2018-04-27 16:22:52,207 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Preparing: SELECT a.id, a.user_name userName, a.user_password userPassword, a.user_email userEmail, a.user_info userInfo, a.head_img headImg, a.create_time createTime FROM sys_user a WHERE user_name like concat('%',?,'%') and user_email = ? 2018-04-27 16:22:52,207 DEBUG [main] (BaseJdbcLogger.java:145) - ==> Parameters: x(String), admin@artisan.com(String) 2018-04-27 16:22:52,207 DEBUG [main] (BaseJdbcLogger.java:145) - <== Total: 0 2018-04-27 16:22:52,207 INFO [main] (UserMapperTest.java:995) - [] 2018-04-27 16:22:52,217 INFO [main] (UserMapperTest.java:1001) - sqlSession close successfully