ORM整合Spring
# 一、需求背景
先前我们实现了一个mini-mybatis,通过对配置文件的加载,XML映射、JDBC的封装,实现了对数据的CRUD
操作。现在,我们要将这个简易的ORM
框架注入到Spring
中,把框架的管理交给Spring
容器。
# 二、方案设计
对于我们的ORM
框架,需要把一些诸如mapper的扫描、SqlSession工厂创建等交给Spring容器中,由Spring容器进行管理。
- 在方案设计中包括:需要注册对象的扫描、代理类的实现、Bean的注册
- 通过SqlSessionFactoryBuilder创建连接工厂,连接到ORM框架。
# 三、实现方案
# 1、依赖注入
需要将先前我们实现的简易ORM
框架引入进来:
<dependency>
<groupId>me.xu</groupId>
<artifactId>spring-mini-mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
# 2、SqlSession工厂实例注入
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 {
try (Reader reader = Resources.getResourceAsReader(resource)) {
this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setResource(String resource) {
this.resource = resource;
}
}
该类主要用于mybatis的核心加载,比如资源、SqlSessionFactory
。
afterPropertiesSet
:调用SqlSessionFactoryBuilder
的构造方法
# 3、Mapper扫描类
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor {
private String basePackage;
private SqlSessionFactory sqlSessionFactory;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
try {
String packageSearchPath = "classpath*:" + basePackage.replace('.', '/') + "/*.class";
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
for (Resource resource : resources) {
MetadataReader metadataReader = new SimpleMetadataReader(resource, ClassUtils.getDefaultClassLoader());
ScannedGenericBeanDefinition beanDefinition = new ScannedGenericBeanDefinition(metadataReader);
String beanName = Introspector.decapitalize(ClassUtils.getShortName(beanDefinition.getBeanClassName()));
beanDefinition.setResource(resource);
beanDefinition.setSource(resource);
beanDefinition.setScope("singleton");
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(sqlSessionFactory);
beanDefinition.setBeanClass(MapperFactoryBean.class);
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
}
public class SimpleMetadataReader implements MetadataReader {
private final Resource resource;
private final ClassMetadata classMetadata;
private final AnnotationMetadata annotationMetadata;
public SimpleMetadataReader(Resource resource, ClassLoader classLoader) throws IOException {
ClassReader classReader;
try (InputStream is = new BufferedInputStream(resource.getInputStream())) {
classReader = new ClassReader(is);
} catch (IllegalArgumentException ex) {
throw new NestedIOException("ASM ClassReader failed to parse class file - " +
"probably due to a new Java class file version that isn't supported yet: " + resource, ex);
}
AnnotationMetadataReadingVisitor visitor = new AnnotationMetadataReadingVisitor(classLoader);
classReader.accept(visitor, ClassReader.SKIP_DEBUG);
this.annotationMetadata = visitor;
// (since AnnotationMetadataReadingVisitor extends ClassMetadataReadingVisitor)
this.classMetadata = visitor;
this.resource = resource;
}
@Override
public Resource getResource() {
return this.resource;
}
@Override
public ClassMetadata getClassMetadata() {
return this.classMetadata;
}
@Override
public AnnotationMetadata getAnnotationMetadata() {
return this.annotationMetadata;
}
}
在设置了基本路径之后,就需要把所有的Mapper接口扫描出来,完成代理和注册到Spring Bean容器里面。
- 根据基础路径,扫描到下面所有资源
- 遍历资源,获取到class信息,用于注册bean
# 4、代理类
public class MapperFactoryBean<T> implements FactoryBean<T> {
private Logger logger = LoggerFactory.getLogger(MapperFactoryBean.class);
private Class<T> mapperInterface;
private SqlSessionFactory sqlSessionFactory;
public MapperFactoryBean(Class<T> mapperInterface, SqlSessionFactory sqlSessionFactory) {
this.mapperInterface = mapperInterface;
this.sqlSessionFactory = sqlSessionFactory;
}
@Override
public T getObject() throws Exception {
InvocationHandler handler = (proxy, method, args) -> {
logger.info("你被代理了,执行SQL操作!{}", method.getName());
if ("toString".equals(method.getName())) {
return null; // 排除Object方法
}
try {
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;
}
}
getObject()
是一个Java代理类的实现,这个代理类对象会挂到注入中,真正调用方法内容时候,会执行到代理类的实现部分。
# 四、案例测试
# 1、配置文件
数据源配置
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/Student_Mapper.xml"/>
</mappers>
</configuration>
Spring Bean配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-autowire="byName">
<context:component-scan base-package="me.xu"/>
<aop:aspectj-autoproxy/>
<bean id="sqlSessionFactory" class="me.xu.mybatis.SqlSessionFactoryBean">
<property name="resource" value="mybatis-config-datasource.xml"/>
</bean>
<bean class="me.xu.mybatis.MapperScannerConfigurer">
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="me.xu.mybatis.mapper"/>
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
</beans>
# 2、Bean和Mapper
Bean
public class Student {
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
private String id;
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
mapper
public interface StudentMapper {
Student queryStuInfoByName(String name);
}
<?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 namespace="me.xu.mybatis.mapper.StudentMapper">
<select id="queryStuInfoByName" parameterType="java.lang.String" resultType="me.xu.mybatis.bean.Student">
SELECT id, name, age
FROM student
where name = #{name}
</select>
</mapper>
# 3、接口测试
public class ApiTest {
@Test
public void queryStuInfoByName() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
StudentMapper studentMapper = beanFactory.getBean("studentMapper", StudentMapper.class);
Student student = studentMapper.queryStuInfoByName("学生02");
System.out.println(student);
}
}
# 参考
上次更新: 2024/06/29, 15:13:44