文章目录
MyBatis-Spring的基本使用
MyBatis-Spring中间件 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring
构建MyBatis和Spring整合的首先需要引入对应的依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.4</version>
</dependency>
要整合Spring和MyBatis需要俩个东西:
1:一个 SqlSessionFactory
2:至少一个数据映射器类
SqlSessionFactory
@Configuration
public class MyConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
private DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("admin");
return driverManagerDataSource;
}
}
配置SqlSessionFactory 可以通过Java代码的方式,也可以通过XML配置文件的方式:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>admin</value>
</property>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="typeAliasesPackage" value="com.octopus.pojo" />
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:com/octopus/mapper/*.xml"/>
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置参数,一行配置一个 -->
<value>
</value>
</property>
</bean>
</array>
</property>
</bean>
如此,便是完成了IOC容器中注入SqlSessionFactory 的实例对象
Mapper是一个接口,可以通过它来操作数据,第一种通过在方法上添加注解,第二种创建对应的xml文件,第一种适合于SQL相对简单的操作,XML适合于相对复杂的SQL语句
此时,当我们完成了Mapper类的编写后,现在他只是一个接口,Spring如何去管理这些对象了,这里肯定不能通过@Component注解,这个原理待会在详细说,先看下Spring提供给开发人员的配置方法:
方法一:通过@Bean或者XML指定某个Mapper,这种不适合实际开发
方法二:通过@MapperScan(“XXX”)注解扫描
方法三:多个包可以通过,;分割
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.octopus.XXXX1,;com.octopus.XXXX1"/>
</bean>
构建自己的Mapper
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserMapper userMapper = (UserMapper)context.getBean("userMapper");
System.out.println(userMapper);
}
通过Spring和MyBatis整合后,发现注入到IOC容器中的Mapper对象,其实是一个代理类对象,即Spring是通过动态代理,构建一个代理类,然后反射获取对应的方法注解,执行注解上的SQL语句,关联到SqlSession对象最后执行并得到数据库数据记录的
public interface UserMapper {
@Select("SELECT * FROM users ")
public List<User> findUsers();
}
@Test
public void proxyTest() {
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过反射获取注解
System.out.println("注解标注的SQL语句为:" + method.getAnnotation(Select.class).value()[0]);
return null;
}
});
userMapper.findUsers();
}
代理类生成的对象如何添加到单例池中呢?
方式一:BeanFactoryPostProcessor
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("user");
beanDefinition.setBeanClassName("com.octopus.mapper.UserMapper");
}
}
XXXPostProcessor阅读过Spring都知道这是Spring的后置处理器会在IOC容器初始化的时候被调用,对相关的Bean进行处理
通过构建自己的后置处理器可以操作对应的Bean,这种方式无法直接添加Bean,只能做原有Bean的修改,可以扩展Bean对象功能
上面是通过先获取某个Bean的方式,通过该方式,我们可以操作BeanFactory来往单例池添加一个对象,但是这种方式不适合开发中实际使用,无法进一步扩展
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
beanFactory.registerSingleton("userMapper", userMapper);
}
}
方式二:FactoryBean
Spring提供了一个org.springframework.bean.FactoryBean的工厂接口,用户可以通过实现该接口定制实例化的bean。
FactoryBean接口对于Spring框架来说占用重要的地位,Spring本身就提供了特别多的FactoryBean的实现。它们隐藏了实例化复杂bean的细节,给上层应用带来了便利。FactoryBean的源码如下
public interface FactoryBean<T> {
//返回由FactoryBean创建的bean实例,如果isSingleton()返回true,
//则该实例会放到Spring容器中实例缓存池中
@Nullable
T getObject() throws Exception;
//返回FactoryBean创建的 bean的类型。
@Nullable
Class<?> getObjectType();
//返回有FactoryBean创建的bean的实例的作用域是singleton还是prototype,
//这里默认返回的是true,也就是默认是singleton bean
default boolean isSingleton() {
return true;
}
}
&factoryBean——代表实现FactoryBean的普通类
factoryBean——表示返回getObject中的Bean对象
@Component
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
@Test
public void testFactoryBean(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println(context.getBean("&myFactoryBean"));
}
@Test
public void testFactoryBean(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println(context.getBean("myFactoryBean"));
}
Spring源码中会去判断实现了FactoryBean 接口的类,如果BeanName前缀是&则表示的为一个普通的Bean,如果不带&则构建的Bean就是调用getObject()方法,将这个方法的得到的对象加入到单例池中
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
上面是Spring中管理MyBatis的方式,可以看到对应的MapperFactoryBean
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
private Class<T> mapperInterface;
private boolean addToConfig = true;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public MapperFactoryBean() {
}
/**
* {@inheritDoc}
*/
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
}
该类实现了FactoryBean接口
通过这个可以发现,Spring就是通过这个实现中间件的类注册,但是现在还存在一个问题,就是自定义的MyFactoryBean如何注册到IOC容中,上面使用的是@Component但是这个肯定不行的,Spring默认扫描的包@ComponentScan肯定不会扫描到这些中间件的具体路径的,所以现在需要解决的问题就是如何注册当期的MyFactoryBean
@Import
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* The @{@link Configuration}, {@link ImportSelector} and/or
* {@link ImportBeanDefinitionRegistrar} classes to import.
*/
Class<?>[] value();
}
@Import为我们提供俩种实现方式:
ImportSelector
ImportBeanDefinitionRegistrar
现在去除掉上面MyFactoryBean中的@component注解,创建下面对象
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
registry.registerBeanDefinition("userMapper",beanDefinition);
}
}
然后在配置类上添加@Import注解即可
@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
private DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("admin");
return driverManagerDataSource;
}
}
创建自定义的@MapperScan
经过上面的步骤已经实现用户可以通过@Import(MyImportBeanDefinitionRegistrar.class)注册Mapper,但是我们知道MyBatis-Spring提供了@MapperScan注解来简化这些操作,现在我们也同样需要构建一个注解@MyMapperScan,并且该注解可以提供扫描包的功能,需要将包下所有的Mapper都添加到单例池中
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {
String[] value() default {};
}
上面就是自定义的一个注解,可以看到注解类导入了
@Import(MyImportBeanDefinitionRegistrar.class)
通过上面构建后,还需要对原来的ImportBeanDefinitionRegistrar进行修改,使得它具有如下功能
1:注入Bean
2:获取自定义注解@MyMapperScan(“xxx”)下的所有的Mapper接口
3:将Mapper以首字母小写作为BeanName,Mapper接口遍历注入
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取注解信息
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
//存储包路径
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
List<Class<?>> classes = null;
try {
//工具类,根据包名获取包下所有的类限定名
classes = PackageScanner.PackageScanner(basePackages);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//遍历每个获取到的Mapper接口,注入IOC中
for (Class<?> aClass : classes) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
//构造器传参
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getName());
System.out.println(aClass.getName());
String[] split = aClass.getName().split("\\.");
String beanName = split[split.length - 1];
String name = beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
registry.registerBeanDefinition(name, beanDefinition);
}
}
}
由于MyFactoryBean 需要动态的创建Bean,因此里面的动态代理创建的对象我们需要通过外部传入进去,不能写死,这里通过构造器传入一个Class类型的对象,就是一个接口类型,然后在 BeanDefinitionRegistrar中通过构造器创建FactoryBean对象:
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getName());
public class MyFactoryBean implements FactoryBean {
private Class mapper;
public MyFactoryBean(Class mapper) {
this.mapper = mapper;
}
@Override
public Object getObject() throws Exception {
Object object = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{mapper}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("注解标注的SQL语句为:" + method.getAnnotation(Select.class).value()[0]);
return null;
}
});
return object;
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
截止目前已经基本完成了中间件的简单构建,现在创建俩个Mapper接口,然后进行测试
public interface EmpMapper {
@Select("SELECT * FROM emp ")
public List<Emp> findEmps();
}
public interface UserMapper {
@Select("SELECT * FROM users ")
public List<User> findUsers();
}
测试类:
@Test
public void testMyImportBeanDefinitionRegistrar(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserMapper userMapper = (UserMapper) context.getBean("userMapper");
userMapper.findUsers();
EmpMapper empMapper = (EmpMapper) context.getBean("empMapper");
empMapper.findEmps();
}
参考:
http://mybatis.org/spring/zh/index.html
https://www.jianshu.com/p/afd2c49394c2