如何将 ORM 框架整合到 Spring Bean 容器中?
Mapper
和 SqlSession
,并将其注入 Bean,DataAccessException
,实现应用代码不依赖于 Mybatis、Spring 或 Mybatis-Spring。MapperScannerConfigurer
扫描配置目录的方式,将包装了 sqlSessionFactory
对数据库操作细节的 Dao
接口类的代理类 MapperFactoryBean
注册到 Spring Bean
容器中,便于使用 Mybatis
。设计:Spring整合Mybatis
sqlSessionFactory
的调用和对 SqlSession
方法的使用。SqlSessionFactoryBuilder
将 Spring 连接到 ORM 框架。BeanDefinitionRegistryPostProcessor
实现,便于将数据库操作 Mapper
的代理对象注入 Spring Bean 容器中。pom.xml
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.75version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.11version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.2.9version>
dependency>
spring-step-20
|-src
|-main
| |-java
| |-com.lino
| | |-mybatis
| | | |-Configuration.java
| | | |-DefaultSqlSession.java
| | | |-DefaultSqlSessionFactory.java
| | | |-Resources.java
| | | |-SqlSession.java
| | | |-SqlSessionFactory.java
| | | |-SqlSessionFactoryBuilder.java
| | | |-XNode.java
| | |-springframework
| | | |-aop
| | | |-beans
| | | | |-factory
| | | | | |-config
| | | | | |-support
| | | | | | |-AbstractAutowireCapableBeanFactory.java
| | | | | | |-BeanDefinitionRegistryPostProcessor.java
| | | |-context
| | | | |-annotation
| | | | |-event
| | | | |-support
| | | | | |-AbstractApplicationContext.java
| | | |-mybatis
| | | | |-MapperFactoryBean.java
| | | | |-MapperScannerConfigurer.java
| | | | |-SqlSessionFactoryBean.java
|-test
|-java
|-com.lino.springframework.test
|-dao
|-IUserDao.java
|-po
|-User.java
|-ApiTest.java
|-resources
| |-mapper
| | |-User_Mapper.xml
|-mybatis-config-datasource.xml
|-spring.xml
MapperScannerConfigurer
)。MapperFactoryBean
)。SqlSessionFactoryBean
类是对 SqlSessionFactoryBuilder
类的使用,也是实现 FactoryBean
的一个 Bean 对象。XNode.java
package com.lino.mybatis;
import java.util.Map;
/**
* @description: 节点
*/
public class XNode {
private String namespace;
private String id;
private String parameterType;
private String resultType;
private String sql;
private Map<Integer, String> parameter;
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
public Map<Integer, String> getParameter() {
return parameter;
}
public void setParameter(Map<Integer, String> parameter) {
this.parameter = parameter;
}
}
SqlSession.java
package com.lino.mybatis;
import java.util.List;
/**
* @description: SqlSession 接口
*/
public interface SqlSession {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<T> List<T> selectList(String statement);
<T> List<T> selectList(String statement, Object parameter);
void close();
}
SqlSession
,用于查询一个或多个结果,同时包括有参数和无参数的方法。DefaultSqlSession.java
package com.lino.mybatis;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.Date;
import java.util.*;
/**
* @description: 默认会话类
*/
public class DefaultSqlSession implements SqlSession {
private Connection connection;
private Map<String, XNode> mapperElement;
public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
this.connection = connection;
this.mapperElement = mapperElement;
}
@Override
public <T> T selectOne(String statement) {
try {
XNode xNode = mapperElement.get(statement);
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
ResultSet resultSet = preparedStatement.executeQuery();
List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
return objects.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
ResultSet resultSet = preparedStatement.executeQuery();
List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
return objects.get(0);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> List<T> selectList(String statement) {
XNode xNode = mapperElement.get(statement);
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
ResultSet resultSet = preparedStatement.executeQuery();
return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public <T> List<T> selectList(String statement, Object parameter) {
XNode xNode = mapperElement.get(statement);
Map<Integer, String> parameterMap = xNode.getParameter();
try {
PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
buildParameter(preparedStatement, parameter, parameterMap);
ResultSet resultSet = preparedStatement.executeQuery();
return resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {
int size = parameterMap.size();
// 单个参数
if (parameter instanceof Long) {
for (int i = 1; i <= size; i++) {
preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
}
return;
}
if (parameter instanceof Integer) {
for (int i = 1; i <= size; i++) {
preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));
}
return;
}
if (parameter instanceof String) {
for (int i = 1; i <= size; i++) {
preparedStatement.setString(i, parameter.toString());
}
return;
}
Map<String, Object> fieldMap = new HashMap<>();
// 对象参数
Field[] declaredFields = parameter.getClass().getDeclaredFields();
for (Field field : declaredFields) {
String name = field.getName();
field.setAccessible(true);
Object obj = field.get(parameter);
field.setAccessible(false);
fieldMap.put(name, obj);
}
for (int i = 1; i <= size; i++) {
String parameterDefine = parameterMap.get(i);
Object obj = fieldMap.get(parameterDefine);
if (obj instanceof Short) {
preparedStatement.setShort(i, Short.parseShort(obj.toString()));
continue;
}
if (obj instanceof Integer) {
preparedStatement.setInt(i, Integer.parseInt(obj.toString()));
continue;
}
if (obj instanceof Long) {
preparedStatement.setLong(i, Long.parseLong(obj.toString()));
continue;
}
if (obj instanceof String) {
preparedStatement.setString(i, obj.toString());
continue;
}
if (obj instanceof Date) {
preparedStatement.setDate(i, (java.sql.Date) obj);
}
}
}
private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
List<T> list = new ArrayList<>();
try {
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
// 每次遍历行值
while (resultSet.next()) {
T obj = (T) clazz.newInstance();
for (int i = 1; i <= columnCount; i++) {
Object value = resultSet.getObject(i);
String columnName = metaData.getColumnName(i);
String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
Method method;
if (value instanceof Timestamp) {
method = clazz.getMethod(setMethod, Date.class);
} else {
method = clazz.getMethod(setMethod, value.getClass());
}
method.invoke(obj, value);
}
list.add(obj);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
@Override
public void close() {
if (null == connection) return;
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Resources.java
package com.lino.mybatis;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
/**
* @description: 资源处理类
*/
public class Resources {
public static Reader getResourceAsReader(String resource) throws IOException {
return new InputStreamReader(getResourceAsStream(resource));
}
private static InputStream getResourceAsStream(String resource) throws IOException {
ClassLoader[] classLoaders = getClassLoaders();
for (ClassLoader classLoader : classLoaders) {
InputStream inputStream = classLoader.getResourceAsStream(resource);
if (null != inputStream) {
return inputStream;
}
}
throw new IOException("Could not find resource " + resource);
}
private static ClassLoader[] getClassLoaders() {
return new ClassLoader[]{
ClassLoader.getSystemClassLoader(),
Thread.currentThread().getContextClassLoader()};
}
}
Configuration.java
package com.lino.mybatis;
import java.sql.Connection;
import java.util.Map;
/**
* @description: 链接信息和数据源配置类
*/
public class Configuration {
protected Connection connection;
protected Map<String, String> dataSource;
protected Map<String, XNode> mapperElement;
public void setConnection(Connection connection) {
this.connection = connection;
}
public void setDataSource(Map<String, String> dataSource) {
this.dataSource = dataSource;
}
public void setMapperElement(Map<String, XNode> mapperElement) {
this.mapperElement = mapperElement;
}
}
SqlSessionFactory.java
package com.lino.mybatis;
/**
* @description: SqlSessionFactory 接口
*/
public interface SqlSessionFactory {
SqlSession openSession();
}
SqlSession
接口,这是平常使用时都需要操作的内容。当操作数据库时,会获取每一次执行的 SqlSession
。DefaultSqlSessionFactory.java
package com.lino.mybatis;
/**
* @description: 默认会话类工厂
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration.connection, configuration.mapperElement);
}
}
DefaultSqlSessionFactory
是 Mybatis 最常用的类,这里实现了一个简单版本。
SqlSession
时,会返回一个 DefaultSqlSession
构造函数。这个构造函数向下传递了 Configuration
配置文件。Connection connection
、Map dataSource
、Map mapperElement
。SqlSessionFactoryBuilder.java
package com.lino.mybatis;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @description: SqlSessionFactoryBuilder
*/
public class SqlSessionFactoryBuilder {
public DefaultSqlSessionFactory build(Reader reader) {
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(new InputSource(reader));
Configuration configuration = parseConfiguration(document.getRootElement());
return new DefaultSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
public DefaultSqlSessionFactory build(InputStream inputStream) {
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(inputStream);
Configuration configuration = parseConfiguration(document.getRootElement());
return new DefaultSqlSessionFactory(configuration);
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
private Configuration parseConfiguration(Element root) {
Configuration configuration = new Configuration();
configuration.setDataSource(dataSource(root.element("environments").element("environment").element("dataSource")));
configuration.setConnection(connection(configuration.dataSource));
configuration.setMapperElement(mapperElement(root.element("mappers")));
return configuration;
}
// 获取数据源配置信息
private Map<String, String> dataSource(Element element) {
Map<String, String> dataSource = new HashMap<>(4);
List<Element> propertyList = element.elements("property");
for (Element e : propertyList) {
String name = e.attributeValue("name");
String value = e.attributeValue("value");
dataSource.put(name, value);
}
return dataSource;
}
private Connection connection(Map<String, String> dataSource) {
try {
Class.forName(dataSource.get("driver"));
return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}
// 获取SQL语句信息
private Map<String, XNode> mapperElement(Element mappers) {
Map<String, XNode> map = new HashMap<>();
List<Element> mapperList = mappers.elements("mapper");
for (Element e : mapperList) {
String resource = e.attributeValue("resource");
try {
Reader reader = Resources.getResourceAsReader(resource);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new InputSource(reader));
Element root = document.getRootElement();
//命名空间
String namespace = root.attributeValue("namespace");
// SELECT
List<Element> selectNodes = root.elements("select");
for (Element node : selectNodes) {
String id = node.attributeValue("id");
String parameterType = node.attributeValue("parameterType");
String resultType = node.attributeValue("resultType");
String sql = node.getText();
// ? 匹配
Map<Integer, String> parameter = new HashMap<>();
Pattern pattern = Pattern.compile("(#\\{(.*?)})");
Matcher matcher = pattern.matcher(sql);
for (int i = 1; matcher.find(); i++) {
String g1 = matcher.group(1);
String g2 = matcher.group(2);
parameter.put(i, g2);
sql = sql.replace(g1, "?");
}
XNode xNode = new XNode();
xNode.setNamespace(namespace);
xNode.setId(id);
xNode.setParameterType(parameterType);
xNode.setResultType(resultType);
xNode.setSql(sql);
xNode.setParameter(parameter);
map.put(namespace + "." + id, xNode);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
return map;
}
}
SqlSessionFactoryBuilder
类包括的核心方法。
build
(构建实例化元素)
build
类:主要用于创建解析 XML 文件中的类,以及初始化 SqlSession
工厂中的类 DefaultSqlSessionFactory
。saxReader.read(new InputSource(reader))
获取 XML 配置文件。parseConfiguration
(解析配置)
dataSource
数据源配置和 mappers
映射语句配置。connection(Map dataSource)
(链接数据库)
Class.forName(dataSource.get("driver"))
来链接数据库。mapperElement
(解析 SQL 语句)
BeanDefinitionRegistryPostProcessor.java
package com.lino.springframework.beans.factory.support;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.config.BeanFactoryPostProcessor;
/**
* @description: Bean对象注册接口
*/
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
/**
* 注册Bean对象
*
* @param registry Bean注册对象
* @throws BeansException 异常
*/
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
BeanDefinitionRegistryPostProcessor
类继承了 BeanFactoryPostProcessor
类。
BeanDefinitionRegistryPostProcessor
类的接口,即可主动向 Spring Bean
容器注册 Bean 对象。AbstractApplicationContext.java
package com.lino.springframework.context.support;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.lino.springframework.beans.factory.config.BeanFactoryPostProcessor;
import com.lino.springframework.beans.factory.config.BeanPostProcessor;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import com.lino.springframework.context.ApplicationEvent;
import com.lino.springframework.context.ApplicationListener;
import com.lino.springframework.context.ConfigurableApplicationContext;
import com.lino.springframework.context.event.ApplicationEventMulticaster;
import com.lino.springframework.context.event.ContextClosedEvent;
import com.lino.springframework.context.event.ContextRefreshedEvent;
import com.lino.springframework.context.event.SimpleApplicationEventMulticaster;
import com.lino.springframework.core.convert.ConversionService;
import com.lino.springframework.core.io.DefaultResourceLoader;
import java.util.Collection;
import java.util.Map;
/**
* @description: 抽象应用上下文
*/
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
public static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "applicationEventMulticaster";
private ApplicationEventMulticaster applicationEventMulticaster;
@Override
public void refresh() throws BeansException {
// 1.创建 BeanFactory,并加载 BeanDefinition
refreshBeanFactory();
// 2.获取 BeanFactory
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 3.添加 ApplicationContextAwareProcessor,让继承自 ApplicationContextAware 的 Bean 对象都能感知所属的 ApplicationContext
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
// 4.在 Bean 实例化之前,执行 BeanFactoryPostProcess
invokeBeanFactoryPostProcessor(beanFactory);
// 5.BeanPostProcessor 需要提前与其他 Bean 对象实例化之前执行注册操作
registerBeanPostProcessor(beanFactory);
// 6.初始化事件发布者
initApplicationEventMulticaster();
// 7.注册事件监听器
registerListeners();
// 8.设置类型转换器、提前实例化单例 Bean 对象
finishBeanFactoryInitialization(beanFactory);
// 9.发布容器刷新完成事件
finishRefresh();
}
...
private void invokeBeanFactoryPostProcessor(ConfigurableListableBeanFactory beanFactory) {
Map<String, BeanFactoryPostProcessor> beanFactoryPostProcessorMap = beanFactory.getBeansOfType(BeanFactoryPostProcessor.class);
for (BeanFactoryPostProcessor beanFactoryPostProcessor : beanFactoryPostProcessorMap.values()) {
beanFactoryPostProcessor.postProcessBeanFactory(beanFactory);
}
// 注册对象
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessorMap.values()) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryPostProcessor = (BeanDefinitionRegistryPostProcessor) postProcessor;
registryPostProcessor.postProcessBeanDefinitionRegistry(registry);
}
}
}
}
...
}
AbstractApplicationContext#invokeBeanFactoryPostProcessor
中扩展对 BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
的调用,并注册 Bean 对象,这样就可以将 ORM 中对数据库的操作对象注册进来。AbstractAutowireCapableBeanFactory.java
package com.lino.springframework.beans.factory.support;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.TypeUtil;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.*;
import com.lino.springframework.beans.factory.config.*;
import com.lino.springframework.core.convert.ConversionService;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
/**
* @description: 实现默认bean创建的抽象bean工厂超类
*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
private void applyPropertyValues(String beanName, Object bean, BeanDefinition beanDefinition) {
try {
PropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue propertyValue : propertyValues.getPropertyValues()) {
String name = propertyValue.getName();
Object value = propertyValue.getValue();
if (value instanceof BeanReference) {
// A 依赖 B,获取 B 的实例化
BeanReference beanReference = (BeanReference) value;
value = getBean(beanReference.getBeanName());
}
// // 类型转换
// else {
// Class> sourceType = value.getClass();
// Class> targetType = (Class>) TypeUtil.getFieldType(bean.getClass(), name);
// ConversionService conversionService = getConversionService();
// if (conversionService != null) {
// if (conversionService.canConvert(sourceType, targetType)) {
// value = conversionService.convert(value, targetType);
// }
// }
// }
// 属性填充
BeanUtil.setFieldValue(bean, name, value);
}
} catch (Exception e) {
throw new BeansException("Error setting property values: " + beanName + " message: " + e);
}
}
...
}
SqlSessionFactoryBean.java
package com.lino.springframework.mybatis;
import com.lino.mybatis.SqlSessionFactory;
import com.lino.mybatis.SqlSessionFactoryBuilder;
import com.lino.springframework.beans.factory.FactoryBean;
import com.lino.springframework.beans.factory.InitializingBean;
import com.lino.springframework.core.io.DefaultResourceLoader;
import com.lino.springframework.core.io.Resource;
import java.io.InputStream;
/**
* @description: 会话工厂
*/
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean {
private String resource;
private SqlSessionFactory sqlSessionFactory;
@Override
public SqlSessionFactory getObject() throws Exception {
return sqlSessionFactory;
}
@Override
public Class<?> getObjectType() {
return SqlSessionFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
DefaultResourceLoader defaultResourceLoader = new DefaultResourceLoader();
Resource resource = defaultResourceLoader.getResource(this.resource);
try (InputStream inputStream = resource.getInputStream()) {
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setResource(String resource) {
this.resource = resource;
}
}
SqlSessionFactoryBean
类主要实现了 FactoryBean、InitializingBean
类,用于加载 Mybatis 核心流程类。
InitializingBean
类主要用于加载 Mybatis 相关的内容。如:解析 XML、构造 SqlSession、链接数据库等。FactoryBean
类主要包括 getObject
方法、getObjectType
方法和 isSingleton
方法。MapperScannerConfigurer.java
package com.lino.springframework.mybatis;
import cn.hutool.core.lang.ClassScanner;
import com.lino.mybatis.SqlSessionFactory;
import com.lino.springframework.beans.BeansException;
import com.lino.springframework.beans.PropertyValue;
import com.lino.springframework.beans.PropertyValues;
import com.lino.springframework.beans.factory.ConfigurableListableBeanFactory;
import com.lino.springframework.beans.factory.config.BeanDefinition;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistry;
import com.lino.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import java.util.Set;
/**
* @description: Mapper 扫描配置,根据包信息扫描接口类并注册
*/
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {
private String basePackage;
private SqlSessionFactory sqlSessionFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
try {
Set<Class<?>> classes = ClassScanner.scanPackage(basePackage);
for (Class<?> clazz : classes) {
// Bean 对象定义
BeanDefinition beanDefinition = new BeanDefinition(clazz);
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(new PropertyValue("mapperInterface", clazz));
propertyValues.addPropertyValue(new PropertyValue("sqlSessionFactory", sqlSessionFactory));
beanDefinition.setPropertyValues(propertyValues);
beanDefinition.setBeanClass(MapperFactoryBean.class);
// Bean 对象注册
registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
}
MapperScannerConfigurer
类处理的核心内容是将用户配置在 XML 中 Dao 接口类的地址全部扫描出来,并将代理对象注册到 Spring Bean 容器中。
ClassScanner.scanPackage(basePackage)
处理类的扫描注册 classpath:com.lino.springframework.test.dao.**.class
,解析 class
文件来获取资源信息。beanDefinition.setBeanClass(MapperFactoryBean.class)
,同时在前面设置了相应的属性信息。registry.registerBeanDefinition(clazz.getSimpleName(), beanDefinition)
。MapperFactoryBean.java
package com.lino.springframework.mybatis;
import com.lino.mybatis.SqlSessionFactory;
import com.lino.springframework.beans.factory.FactoryBean;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @description: 数据库操作映射工厂对象。每一个 DAO 接口对应的 Mapper 的代理对象,通过代理对象完成数据库的操作
*/
public class MapperFactoryBean<T> implements FactoryBean<T> {
private Class<T> mapperInterface;
private SqlSessionFactory sqlSessionFactory;
public MapperFactoryBean() {
}
public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) {
this.mapperInterface = mapperInterface;
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public T getObject() throws Exception {
InvocationHandler handler = (proxy, method, args) -> {
// 排除 Object 方法;toString、hashCode
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
try {
System.out.println("你被代理了,执行SQL操作!" + method.getName());
return sqlSessionFactory.openSession().selectOne(mapperInterface.getName() + "." + method.getName(), args[0]);
} catch (Exception e) {
e.printStackTrace();
}
return method.getReturnType().newInstance();
};
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{mapperInterface}, handler);
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
}
MapperFactoryBean
类非常重要,所有的 Dao 接口类实际上都是它,它对 SQL 的所有操作进行分发处理。
T getObject()
是一个 Java 代理类的实现,这个代理类对象会被注入 Spring Bean
容器中。
你被代理了,执行SQL操作!
。InvocationHandler
代理类的实现较简单,主要开启 SqlSession
,并通过固定的 key
返回 SQL 操作。User.java
package com.lino.springframework.test.po;
import java.util.Date;
/**
* @description: 用户类
*/
public class User {
/**
* 主键ID
*/
private Long id;
/**
* 用户ID
*/
private String userId;
/**
* 用户头像
*/
private String userHead;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserHead() {
return userHead;
}
public void setUserHead(String userHead) {
this.userHead = userHead;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}
IUserDao.java
package com.lino.springframework.test.dao;
import com.lino.springframework.test.po.User;
/**
* @description: 用户DAO接口
*/
public interface IUserDao {
/**
* 根据用户ID获取用户信息
*
* @param id 用户ID
* @return 用户信息
*/
User queryUserInfoById(Long id);
}
User_Mapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.springframework.test.dao.IUserDao">
<select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.lino.springframework.test.po.User">
SELECT id, userId, userHead, createTime
FROM user
where id = #{id}
select>
mapper>
mybatis-config-datasource.xml.java
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring?useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/User_Mapper.xml"/>
mappers>
configuration>
spring.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="sqlSessionFactory" class="com.lino.springframework.mybatis.SqlSessionFactoryBean">
<property name="resource" value="classpath:mybatis-config-datasource.xml"/>
bean>
<bean class="com.lino.springframework.mybatis.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="basePackage" value="com.lino.springframework.test.dao"/>
bean>
beans>
ApiTest.java
@Test
public void test_ClassPathXmlApplicationContext() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:spring.xml");
IUserDao userDao = beanFactory.getBean("IUserDao", IUserDao.class);
User user = userDao.queryUserInfoById(1L);
System.out.println("测试结果:" + JSON.toJSONString(user));
}
测试结果
你被代理了,执行SQL操作!queryUserInfoById
测试结果:{"createTime":1670572325000,"id":1,"userHead":"01_50","userId":"184172133"}
MapperFactoryBean
、MapperScannerConfigurer
、SqlSessionFactoryBean
这些核心关键类后,可以将 Spring 与 Mybatis 结合起来使用,解决了没有实现类的接口不能处理数据库的增、删、改、查操作的问题。