在单表操作上,通用 Mapper 已经展现出了它强大的作用。在通用 Mapper 的帮助下,开发人员无需编写繁琐的 xml 文件,即可实现增删改查的功能。因此,当我们的项目仅需要简单的单表操作时,通用 Mapper 无疑是上上之选。
目前网上关于通用 Mapper 集成的案例铺天盖地,但是大部分都是基于 Spring 或者 SpringBoot。对于经常使用框架的同学来说,这当然是极好的。但是当我们没有使用框架或者我们的项目过于简单而远不需要框架时,怎么办?所幸,通用 Mapper 支持原生 Java 编码方式集成,我们无需使用开发框架即可将通用 Mapper 整合到我们的项目里。
可惜的是,目前在网上鲜有见到基于 Java 编码的集成案例,即便在官方说明文档里,对 Java 编码集成方式也只是三言两语略过。于是,笔者下定决心自己研究一下如何使用 Java 编码方式集成通用 Mapper。
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.1version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.38version>
dependency>
<dependency>
<groupId>tk.mybatisgroupId>
<artifactId>mapperartifactId>
<version>4.0.4version>
dependency>
以上版本号在 MySQL 5.6 下亲测可用,若需要使用到其他版本,建议参考官方文档。
正常情况,我们在使用 MyBatis 创建 SqlSessionFactory
对象时,会使用如下代码:
// 加载配置文件
InputStream in = MySqlSessionFactory.class.getClassLoader().getResourceAsStream(resource);
// 创建SqlSessionFactory实例
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
现在,我们需要加入通用 Mapper 的配置:
// 加载配置文件
InputStream in = MySqlSessionFactory.class.getClassLoader().getResourceAsStream(resource);
// 创建SqlSessionFactory实例
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
// 创建一个MapperHelper
MapperHelper mapperHelper = new MapperHelper();
// 特殊配置
Config config = new Config();
// 主键自增回写方法,默认值MYSQL,详细说明请看文档
config.setIDENTITY("MYSQL");
// 支持getter和setter方法上的注解
config.setEnableMethodAnnotation(true);
// 设置 insert 和 update 中,是否判断字符串类型!=''
config.setNotEmpty(true);
// 校验Example中的类型和最终调用时Mapper的泛型是否一致
config.setCheckExampleEntityClass(true);
// 启用简单类型
config.setUseSimpleType(true);
// 枚举按简单类型处理
config.setEnumAsSimpleType(true);
// 自动处理关键字 - mysql
config.setWrapKeyword("`{0}`");
// 设置配置
mapperHelper.setConfig(config);
// 配置 mapperHelper 后,执行下面的操作
mapperHelper.processConfiguration(sqlSessionFactory.getConfiguration());
通过以上配置,通用 Mapper 即可生效。
此外,官方文档还提供了一种在创建 SqlSessionFactory
前配置通用 Mapper 的方法,原理就是使用 tk.mybatis.mapper.session.Configuration
替换 MyBatis 中的 org.apache.ibatis.session.Configuration
,然后使用 new SqlSessionFactoryBuilder().build(configuration)
创建 SqlSessionFactory
。遗憾的是,由于缺少 mybatis-config.xml
文件的解析类,我们很难去配置一个完整的 Configuration
对象,因此该方法并不推荐。(官方也不推荐使用该方法)
通常,我们在使用 MyBatis 时会通过以下代码来获取 SqlSession
:
SqlSession sqlSession = sqlSessionFactory.openSession();
我们知道,SqlSession
对象在使用后要及时 close
掉,避免连接数过多。在使用框架时,我们无需考虑这个问题,这些工作由 SqlSessionTemplate
替我们完成了。而现在,这项工作就只能交由我们自己解决了。
如果像上面那样通过常规的方式获取 SqlSession
,那么每获取一次都需要手动关闭一次,导致我们的项目拥有很多重复性代码,所以现在,我们不这么做。
我们将通过 Java 代理机制,获取 SqlSession
的代理对象,进而由代理对象替我们完成重复性的工作。具体实现代码如下:
public static SqlSession getSqlSessionProxy() {
return (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
private static class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = sqlSessionFactory.openSession(true);
Object object = null;
try {
object = method.invoke(sqlSession, args);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
sqlSession.close();
}
return object;
}
}
从代码可以看出,每次 SqlSession
代理对象被调用时,都会实例化一个 SqlSession
对象,然后执行相应的数据库操作方法,最后再关闭 SqlSession
。基于此,我们的项目只需关注数据库操作,而不用再去担心连接是否及时关闭了。
同样地,在 MyBatis 中我们通常使用下面的代码获取 Mapper
对象:
Mapper mapper = sqlSession.getMapper(TaskMapper.class);
但是现在这个方法是不可行的。因为在上面我们对 SqlSession
进行了切面处理,一旦 SqlSession
对象中的方法被调用,这个连接就会被关闭掉。所以,我们改用下面的方案:
public static <T> T createMapper(Class<? extends Mapper> clazz) {
SqlSession sqlSessionProxy = MySqlSessionFactory.getSqlSessionProxy();
Mapper mapper = MySqlSessionFactory.getConfiguration().getMapper(clazz, sqlSessionProxy);
return (T) mapper;
}
在 SqlSessionFactory
创建时,会将我们项目中配置的 Mapper
注册到 MapperRegistry
对象中,而这个 MapperRegistry
对象正好是 Configuration
配置类的成员变量,因此我们可以直接通过 Configuration
直接获取 Mapper
对象,而无需调用 SqlSession
的 getMapper
方法。
注:实际上,SqlSession
的实现类 DefaultSqlSession
也是通过 Configuration
来获取 Mapper
的。
现在,我们可以直接在业务层进行数据库的增删改查:
public TaskDO selectOneById(int id) {
TaskMapper mapper = MapperFactory.createMapper(TaskMapper.class);
Example example = new Example(TaskDO.class);
example.and().andEqualTo("id", id);
return mapper.selectOneByExample(example);
}
<configuration>
<properties resource="database.properties">properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper class="com.panda.dao.common.mapper.TaskMapper"/>
mappers>
configuration>
package com.panda.dao;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tk.mybatis.mapper.entity.Config;
import tk.mybatis.mapper.mapperhelper.MapperHelper;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* sqlsessionFactory
*
* @author panda
* @date 2018/12/10
*/
public class MySqlSessionFactory {
private static final Logger logger = LoggerFactory.getLogger(MySqlSessionFactory.class);
private static final String resource = "mybatis-conf.xml";
private static SqlSessionFactory sqlSessionFactory;
static {
// 加载配置文件
InputStream in = MySqlSessionFactory.class.getClassLoader().getResourceAsStream(resource);
// 创建SqlSessionFactory实例
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
// 创建一个MapperHelper
MapperHelper mapperHelper = new MapperHelper();
// 特殊配置
Config config = new Config();
// 主键自增回写方法,默认值MYSQL,详细说明请看文档
config.setIDENTITY("MYSQL");
// 支持getter和setter方法上的注解
config.setEnableMethodAnnotation(true);
// 设置 insert 和 update 中,是否判断字符串类型!=''
config.setNotEmpty(true);
// 校验Example中的类型和最终调用时Mapper的泛型是否一致
config.setCheckExampleEntityClass(true);
// 启用简单类型
config.setUseSimpleType(true);
// 枚举按简单类型处理
config.setEnumAsSimpleType(true);
// 自动处理关键字 - mysql
config.setWrapKeyword("`{0}`");
// 设置配置
mapperHelper.setConfig(config);
// 配置 mapperHelper 后,执行下面的操作
mapperHelper.processConfiguration(sqlSessionFactory.getConfiguration());
}
public static Configuration getConfiguration() {
return sqlSessionFactory.getConfiguration();
}
public static SqlSession getSqlSessionProxy() {
return (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
private static class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = sqlSessionFactory.openSession(true);
Object object = null;
try {
object = method.invoke(sqlSession, args);
} catch (Exception e) {
logger.error(e.getMessage(), e);
} finally {
sqlSession.close();
}
return object;
}
}
}
package com.panda.dao;
import org.apache.ibatis.session.SqlSession;
import tk.mybatis.mapper.common.Mapper;
/**
* mapperFactory
*
* @author panda
* @date 2018/12/10
*/
public class MapperFactory {
/**
* 创建 mapper 实例
*
* @param clazz
* @param
* @return
*/
public static <T> T createMapper(Class<? extends Mapper> clazz) {
SqlSession sqlSessionProxy = MySqlSessionFactory.getSqlSessionProxy();
Mapper mapper = MySqlSessionFactory.getConfiguration().getMapper(clazz, sqlSessionProxy);
return (T) mapper;
}
}