Spring Boot 入门实战(7)--JdbcTempalte、Mybatis、动态数据源及 Atomicos 整合(XA 事务)

本文主要介绍 JdbcTempalte、Mybatis 在动态数据源下的配置及使用,实际的应用里可以根据情况选择其中之一或同时使用;动态数据源使用自定义的注解通过切面来实现数据源的切换;事务使用基于 Atomicos 的 XA 事务。文中所使用到的软件版本:Spring Boot 2.4.4、jdk1.8.0_181、Mybatis 3.5.6、Atomicos 4.0.6。

1、工程整体结构

2、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.abc.demo</groupId>
    <artifactId>demosb-dd</artifactId>
    <version>1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath />
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
        </dependency>

        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.2.0</version>
            <!--systemPath>E:/bin/jar/oracle/ojdbc6.jar</systemPath>
            <scope>system</scope-->
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
pom.xml

3、application.yml

spring:
  datasource:
    atomic:
      datasource1:
        uniqueResourceName: datasource1
        xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
        testQuery: select 1
        maxPoolSize: 2
        minPoolSize: 2
        poolSize: 2
        borrowConnectionTimeout: 60
        reapTimeout: 360
        maxIdleTime: 60
        maintenanceInterval: 60
        loginTimeout: 60
        xaProperties:
          URL: jdbc:mysql://10.49.196.10:3306/itest?useUnicode=true&characterEncoding=UTF-8
          user: root
          password: 123456
      datasource2:
        uniqueResourceName: datasource2
        xaDataSourceClassName: oracle.jdbc.xa.client.OracleXADataSource
        testQuery: select 1 from dual
        maxPoolSize: 2
        minPoolSize: 2
        poolSize: 2
        borrowConnectionTimeout: 60
        reapTimeout: 360
        maxIdleTime: 60
        maintenanceInterval: 60
        loginTimeout: 60
        xaProperties:
          URL: jdbc:oracle:thin:@10.49.196.10:1521:test
          user: test
          password: 123456

4、配置

4.1、动态数据源配置

4.1.1、自定义数据源注解

package com.abc.demo.datasource;

import java.lang.annotation.*;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
    String value() default "";
}

4.1.2、自定义数据源切面

package com.abc.demo.datasource;


import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DataSourceAspect {
    private static Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

    @Pointcut("@annotation(com.abc.demo.datasource.DataSource) || @within(com.abc.demo.datasource.DataSource)")
    public void dataSourcePointCut() {
        Object d;
    }

    //@Around("dataSourcePointCut()")
    @Around("execution(* com.abc.demo.dao..*.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class targetClass = point.getTarget().getClass();
        Method method = signature.getMethod();

        String dataSourceName = "";
        DataSource targetDataSource = (DataSource)targetClass.getAnnotation(DataSource.class);
        DataSource methodDataSource = method.getAnnotation(DataSource.class);
        if (targetDataSource != null || methodDataSource != null){
            if (methodDataSource != null) {
                dataSourceName = methodDataSource.value();
            } else {
                dataSourceName = targetDataSource.value();
            }

            //从接口中找注解
        } else {
            for (Class<?> clazz : targetClass.getInterfaces()) {
                Class<?>[] types = method.getParameterTypes();

                //接口上的注解
                if (clazz.isAnnotationPresent(DataSource.class)) {
                    DataSource source = clazz.getAnnotation(DataSource.class);
                    dataSourceName = source.value();
                }

                //接口方法注解
                Method m = clazz.getMethod(method.getName(), types);
                if (m != null && m.isAnnotationPresent(DataSource.class)) {
                    DataSource source = m.getAnnotation(DataSource.class);
                    dataSourceName = source.value();
                }

                if (StringUtils.isNotBlank(dataSourceName)) {
                    break;
                }
            }
        }

        if (StringUtils.isNotBlank(dataSourceName)) {
            DynamicContextHolder.push(dataSourceName);
            logger.info("the datasource is {}", dataSourceName);
        }
        try {
            return point.proceed();
        } finally {
            if (StringUtils.isNotBlank(dataSourceName)) {
                DynamicContextHolder.poll();
            }
            logger.info("clean datasource");
        }
    }
}

4.1.3、动态数据源线程中实际数据源保存

package com.abc.demo.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayDeque;
import java.util.Deque;

public class DynamicContextHolder {
    private static Logger logger = LoggerFactory.getLogger(DynamicContextHolder.class);

    private static final ThreadLocal<Deque<String>> CONTEXT_HOLDER = new ThreadLocal() {
        @Override
        protected Object initialValue() {
            return new ArrayDeque();
        }
    };

    /**
     * 获得当前线程数据源
     * @return 数据源名称
     */
    public static String peek() {
        String key = CONTEXT_HOLDER.get().peek();
        logger.info("key={}", key);
        return key;
    }

    /**
     * 设置当前线程数据源
     * @param dataSource 数据源名称
     */
    public static void push(String dataSource) {
        CONTEXT_HOLDER.get().push(dataSource);
    }

    /**
     * 清空当前线程数据源
     */
    public static void poll() {
        Deque<String> deque = CONTEXT_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            CONTEXT_HOLDER.remove();
        }
    }

}

4.1.4、自定义动态数据源

package com.abc.demo.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicContextHolder.peek();
    }

    public javax.sql.DataSource getTargetDataSource() {
        return this.determineTargetDataSource();
    }

}

4.1.5、事务及动态数据源配置

package com.abc.demo.datasource;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DynamicDataSourceConfig {
    private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceConfig.class);

    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "userTransactionManager", initMethod = "init", destroyMethod = "close")
    public UserTransactionManager userTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    @Bean(name = "xatm")
    @DependsOn({"userTransaction", "userTransactionManager"})
    public PlatformTransactionManager transactionManager() throws Throwable {
        return new JtaTransactionManager(userTransaction(), userTransactionManager());
    }

    @Bean(name = "dataSource1")
    @ConfigurationProperties(prefix="spring.datasource.atomic.datasource1")
    public DataSource dataSource1() {
        return new AtomikosDataSourceBean();
    }

    @Bean(name = "dataSource2")
    @ConfigurationProperties(prefix="spring.datasource.atomic.datasource2")
    public DataSource dataSource2() {
        return new AtomikosDataSourceBean();
    }

    @Bean(name = "dynamicDataSource")
    @DependsOn({"xatm"})
    public DynamicDataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("dataSource1", dataSource1());
        targetDataSources.put("dataSource2", dataSource2());
        dynamicDataSource.setTargetDataSources(targetDataSources);

        dynamicDataSource.setDefaultTargetDataSource(dataSource1());

        return dynamicDataSource;
    }
}

4.2、JdbcTemplate 配置

package com.abc.demo.config;

import com.abc.demo.datasource.DynamicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;


@Configuration
public class JdbcTempalteConfig {
//    @Bean
//    public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) {
//        return new JdbcTemplate(dynamicDataSource);
//    }

    @Bean
    public MyJdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) {
        return new MyJdbcTemplate(dynamicDataSource);
    }

    /**
     * 需重写JdbcTemplate的getDataSource()方法,使之获取实际的数据源,否则在启动事务时不能切换数据源
     */
    class MyJdbcTemplate extends JdbcTemplate {
        public MyJdbcTemplate(DataSource dataSource) {
            super(dataSource);
        }

        @Override
        public DataSource getDataSource() {
            DynamicDataSource dynamicDataSource =  (DynamicDataSource) super.getDataSource();
            DataSource targetDataSource = dynamicDataSource.getTargetDataSource();
            return targetDataSource;
        }
    }
}

4.3、Mybatis配置

4.3.1、重写 SqlSessionTemplate

拷贝一份SqlSessionTemplate代码进行重写,主要需要重写getSqlSessionFactory、getConfiguration方法及内部类SqlSessionInterceptor。

@Override
public SqlSessionFactory getSqlSessionFactory() {
    String key = DynamicContextHolder.peek();
    if (key == null) {
        return defaultSqlSessionFactory;
    }
    return sqlSessionFactorys.get(key);
}

@Override
public org.apache.ibatis.session.Configuration getConfiguration() {
    return this.getSqlSessionFactory().getConfiguration();
}

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
                DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
        try {
            Object result = method.invoke(sqlSession, args);
            if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.sqlSessionFactory)) {
                // force commit even on non-dirty sessions because some databases require
                // a commit/rollback before calling close()
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            if (DynamicSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
                sqlSession = null;
                Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
                        .translateExceptionIfPossible((PersistenceException) unwrapped);
                if (translated != null) {
                    unwrapped = translated;
                }
            }
            throw unwrapped;
        } finally {
            if (sqlSession != null) {
                closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }
}

完整的代码如下:

package com.abc.demo.config;

import com.abc.demo.datasource.DynamicContextHolder;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.*;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.dao.support.PersistenceExceptionTranslator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import static java.lang.reflect.Proxy.newProxyInstance;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
import static org.mybatis.spring.SqlSessionUtils.*;
import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;

/**
 * 重写SqlSessionTemplate
 */
public class DynamicSqlSessionTemplate extends SqlSessionTemplate {
    private SqlSessionFactory sqlSessionFactory;
    private ExecutorType executorType;
    private SqlSession sqlSessionProxy;
    private PersistenceExceptionTranslator exceptionTranslator;

    private SqlSessionFactory defaultSqlSessionFactory = null;
    private Map<String, SqlSessionFactory> sqlSessionFactorys = null;

    public DynamicSqlSessionTemplate(SqlSessionFactory defaultSqlSessionFactory, Map<String, SqlSessionFactory> sqlSessionFactorys) {
        this(defaultSqlSessionFactory);

        this.defaultSqlSessionFactory = defaultSqlSessionFactory;
        this.sqlSessionFactorys = sqlSessionFactorys;
    }

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType,
                new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        super(sqlSessionFactory, executorType, exceptionTranslator);

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
                new Class[] { SqlSession.class }, new SqlSessionInterceptor());
    }

    @Override
    public SqlSessionFactory getSqlSessionFactory() {
        String key = DynamicContextHolder.peek();
        if (key == null) {
            return defaultSqlSessionFactory;
        }
        return sqlSessionFactorys.get(key);
    }

    @Override
    public org.apache.ibatis.session.Configuration getConfiguration() {
        return this.getSqlSessionFactory().getConfiguration();
    }

    public ExecutorType getExecutorType() {
        return this.executorType;
    }

    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.exceptionTranslator;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.selectOne(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.selectOne(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
        return this.sqlSessionProxy.selectMap(statement, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
        return this.sqlSessionProxy.selectMap(statement, parameter, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return this.sqlSessionProxy.selectMap(statement, parameter, mapKey, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> Cursor<T> selectCursor(String statement) {
        return this.sqlSessionProxy.selectCursor(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> Cursor<T> selectCursor(String statement, Object parameter) {
        return this.sqlSessionProxy.selectCursor(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
        return this.sqlSessionProxy.selectCursor(statement, parameter, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> List<E> selectList(String statement) {
        return this.sqlSessionProxy.selectList(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.sqlSessionProxy.selectList(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return this.sqlSessionProxy.selectList(statement, parameter, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void select(String statement, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, handler);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void select(String statement, Object parameter, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, handler);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int insert(String statement) {
        return this.sqlSessionProxy.insert(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int insert(String statement, Object parameter) {
        return this.sqlSessionProxy.insert(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int update(String statement) {
        return this.sqlSessionProxy.update(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int update(String statement, Object parameter) {
        return this.sqlSessionProxy.update(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int delete(String statement) {
        return this.sqlSessionProxy.delete(statement);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int delete(String statement, Object parameter) {
        return this.sqlSessionProxy.delete(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void commit() {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void commit(boolean force) {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rollback() {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void rollback(boolean force) {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clearCache() {
        this.sqlSessionProxy.clearCache();
    }


    /**
     * {@inheritDoc}
     */
    @Override
    public Connection getConnection() {
        return this.sqlSessionProxy.getConnection();
    }

    /**
     * {@inheritDoc}
     *
     * @since 1.0.2
     *
     */
    @Override
    public List<BatchResult> flushStatements() {
        return this.sqlSessionProxy.flushStatements();
    }

    /**
     * Allow gently dispose bean:
     *
     * <pre>
     * {@code
     *
     * <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
     *  <constructor-arg index="0" ref="sqlSessionFactory" />
     * </bean>
     * }
     * </pre>
     *
     * The implementation of {@link DisposableBean} forces spring context to use {@link DisposableBean#destroy()} method
     * instead of {@link SqlSessionTemplate#close()} to shutdown gently.
     *
     * @see SqlSessionTemplate#close()
     * @see "org.springframework.beans.factory.support.DisposableBeanAdapter#inferDestroyMethodIfNecessary(Object, RootBeanDefinition)"
     * @see "org.springframework.beans.factory.support.DisposableBeanAdapter#CLOSE_METHOD_NAME"
     */
    @Override
    public void destroy() throws Exception {
        // This method forces spring disposer to avoid call of SqlSessionTemplate.close() which gives
        // UnsupportedOperationException
    }

    private class SqlSessionInterceptor implements InvocationHandler {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            SqlSession sqlSession = getSqlSession(DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
                    DynamicSqlSessionTemplate.this.executorType, DynamicSqlSessionTemplate.this.exceptionTranslator);
            try {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.sqlSessionFactory)) {
                    // force commit even on non-dirty sessions because some databases require
                    // a commit/rollback before calling close()
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                Throwable unwrapped = unwrapThrowable(t);
                if (DynamicSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
                    closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
                    sqlSession = null;
                    Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
                            .translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                if (sqlSession != null) {
                    closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
                }
            }
        }
    }
}
DynamicSqlSessionTemplate.java

4.3.2、Mybatis 配置

package com.abc.demo.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.annotation.MapperScans;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
@MapperScans(value = {@MapperScan(basePackages = {"com.abc.demo.dao"}, sqlSessionTemplateRef = "dynamicSqlSessionTemplate")})
public class MybatisConfig {
    @Qualifier("dataSource1")
    @Autowired
    private DataSource dataSource1;

    @Qualifier("dataSource2")
    @Autowired
    private DataSource dataSource2;
    
    @Bean("sqlSessionFactory1")
    public SqlSessionFactory sqlSessionFactory1() {
        try {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource1);
            factoryBean.setConfigLocation(
                    new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
            factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/abc/demo/dao/*.xml"));
            return factoryBean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Bean("sqlSessionFactory2")
    public SqlSessionFactory sqlSessionFactory2() {
        try {
            SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource2);
            factoryBean.setConfigLocation(
                    new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
            factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/abc/demo/dao/*.xml"));
            return factoryBean.getObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Bean("dynamicSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate() {
        Map<String, SqlSessionFactory> sqlSessionFactorys = new HashMap<>();
        //这边设置的key需与动态数据源里设置的key一致
        sqlSessionFactorys.put("dataSource1", sqlSessionFactory1());
        sqlSessionFactorys.put("dataSource2", sqlSessionFactory2());
        return new DynamicSqlSessionTemplate(sqlSessionFactory1(), sqlSessionFactorys);
    }
}

5、业务代码

5.1、实体类

package com.abc.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;


@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class School {
    private String name;

    private String location;
}
School.java

School实体类对应a_school表,放在第一个数据库里面。

package com.abc.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class Student {
    private String name;

    private Integer age;
}
Student.java

Student实体类对应a_student表,放在第一二个数据库里面。

5.2、DAO

package com.abc.demo.dao;

import com.abc.demo.datasource.DataSource;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;

import java.util.List;

public interface IBusinessDao {
    @DataSource("dataSource1")
    void insertSchool(School school);

    @DataSource("dataSource1")
    List<School> selectSchool();

    @DataSource("dataSource2")
    void insertStudent(Student student);

    @DataSource("dataSource2")
    List<Student> selectStudent();
}

5.1.1、Jdbc实现

package com.abc.demo.dao.impl;

import com.abc.demo.dao.IBusinessDao;
import com.abc.demo.datasource.DynamicDataSource;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;


@Repository("businessDaoImplJdbc")
public class BusinessDaoImplJdbc implements IBusinessDao {
    private static Logger logger = LoggerFactory.getLogger(BusinessDaoImplJdbc.class);

    @Qualifier("dynamicDataSource")
    @Autowired
    private DataSource dataSource;

    private Connection getConnection() {
        DynamicDataSource dynamicDataSource = (DynamicDataSource)dataSource;
        return DataSourceUtils.getConnection(dynamicDataSource.getTargetDataSource());
    }
    private void releaseConnection(Connection con) {
        DynamicDataSource dynamicDataSource = (DynamicDataSource)dataSource;
        DataSourceUtils.releaseConnection(con, dynamicDataSource.getTargetDataSource());
    }

    @Override
    public void insertSchool(School school) {
        logger.info("insertSchool");
        Connection con = getConnection();
        PreparedStatement pst = null;
        try {
            pst = con.prepareStatement("insert into a_school(name,location) values(?,?)");
            pst.setString(1, school.getName());
            pst.setString(2, school.getLocation());

            pst.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            releaseConnection(con);
        }
    }

    @Override
    public List<School> selectSchool() {
        logger.info("selectSchool");
        Connection con = getConnection();
        Statement st = null;
        ResultSet rs = null;
        List<School> schools = new ArrayList<>();
        try {
            st = con.createStatement();
            rs = st.executeQuery("select * from a_school");
            while (rs.next()) {
                School school = new School();
                school.setName(rs.getString("name"));
                school.setLocation(rs.getString("location"));
                schools.add(school);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            releaseConnection(con);
        }
        return schools;
    }

    @Override
    public void insertStudent(Student student) {
        logger.info("insertStudent");
        Connection con = getConnection();
        PreparedStatement pst = null;
        try {
            pst = con.prepareStatement("insert into a_student(name,age) values(?,?)");
            pst.setString(1, student.getName());
            pst.setInt(2, student.getAge());
            pst.addBatch();

            pst.executeBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            releaseConnection(con);
        }
    }

    @Override
    public List<Student> selectStudent() {
        logger.info("selectStudent");
        Connection con = getConnection();
        Statement st = null;
        ResultSet rs = null;
        List<Student> students = new ArrayList<>();
        try {
            st = con.createStatement();
            rs = st.executeQuery("select * from a_student");
            while (rs.next()) {
                Student student = new Student();
                student.setName(rs.getString("name"));
                student.setAge(rs.getInt("age"));
                students.add(student);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            releaseConnection(con);
        }
        return students;
    }
}

5.1.2、JdbcTemplate实现

package com.abc.demo.dao.impl;

import com.abc.demo.dao.IBusinessDao;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository("businessDaoImplJdbcTemplate")
public class BusinessDaoImplJdbcTemplate implements IBusinessDao {
    private static Logger logger = LoggerFactory.getLogger(BusinessDaoImplJdbcTemplate.class);

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void insertSchool(School school) {
        logger.info("insertSchool");
        jdbcTemplate.update("insert into a_school(name,location) values(?,?)", school.getName(), school.getLocation());
    }

    @Override
    public List<School> selectSchool() {
        logger.info("selectSchool");
        return jdbcTemplate.query("select * from a_school", new BeanPropertyRowMapper<>(School.class));
    }

    @Override
    public void insertStudent(Student student) {
        logger.info("insertStudent");
        jdbcTemplate.update("insert into a_student(name,age) values(?,?)", student.getName(), student.getAge());
    }

    @Override
    public List<Student> selectStudent() {
        logger.info("selectStudent");
        return jdbcTemplate.query("select * from a_student", new BeanPropertyRowMapper<>(Student.class));
    }
}

5.1.3、Mybatis实现

<?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="com.abc.demo.dao.IBusinessDao" >
    <insert id="insertSchool" parameterType="com.abc.demo.entity.School">
        insert into a_school(name,location) values(#{name}, #{location})
    </insert>

    <select id="selectSchool" resultType="com.abc.demo.entity.School">
        select name,location from a_school
    </select>

    <insert id="insertStudent" parameterType="com.abc.demo.entity.Student">
        insert into a_student(name,age) values(#{name}, #{age})
    </insert>

    <select id="selectStudent" resultType="com.abc.demo.entity.Student">
        select name,age from a_student
    </select>
</mapper>

5.3、Service

package com.abc.demo.service;

import java.util.List;
import java.util.Map;

public interface IBusinessService {
    void addSchoolAndStudent();

    Map<String, List<Object>> querySchoolAndStudent();
}
package com.abc.demo.service.impl;

import com.abc.demo.dao.IBusinessDao;
import com.abc.demo.entity.School;
import com.abc.demo.entity.Student;
import com.abc.demo.service.IBusinessService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class BusinessServiceImpl implements IBusinessService {
    private static Logger logger = LoggerFactory.getLogger(BusinessServiceImpl.class);

    /**
     * 可以切换不同的DAO实现,都可以实现同样的效果
     */
    //@Qualifier("IBusinessDao") //Mybatis实现
    //@Qualifier("businessDaoImplJdbc")
    @Qualifier("businessDaoImplJdbcTemplate")
    @Autowired
    private IBusinessDao businessDao;

    @Transactional
    @Override
    public void addSchoolAndStudent() {
        businessDao.insertSchool(new School("南京大学", "南京"));
        businessDao.insertSchool(new School("北京大学", "北京"));
        businessDao.insertStudent(new Student("李白", 20));
        businessDao.insertStudent(new Student("杜甫", 21));
    }

    @Override
    public Map<String, List<Object>> querySchoolAndStudent() {
        List<School> schools = businessDao.selectSchool();
        List<Student> students = businessDao.selectStudent();
        return new HashMap(){{
            put("school", schools);
            put("student", students);
        }};
    }
}

6、Controller

package com.abc.demo.controller;

import com.abc.demo.entity.R;
import com.abc.demo.service.IBusinessService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/business")
public class BusinessController {
    protected static Logger logger = LoggerFactory.getLogger(BusinessController.class);

    @Autowired
    private IBusinessService businessService;


    @RequestMapping("addSchoolAndStudent")
    public R addSchoolAndStudent() {
        businessService.addSchoolAndStudent();
        return R.ok();
    }

    @RequestMapping("querySchoolAndStudent")
    public R querySchool() {
        Map<String, List<Object>> map =  businessService.querySchoolAndStudent();
        return R.ok(map);
    }

}
BusinessController.java

Controller用到的返回对象R:

package com.abc.demo.entity;

/**
 * 返回数据
 */
public class R {
    private static final long serialVersionUID = 1L;

    /**
     * 返回码
     * 0 正常,其他异常
     */
    private int returnCode = 0;

    /**
     * 描述
     */
    private String description = "OK";

    /**
     * 结果数据
     */
    private Object result;

    public int getReturnCode() {
        return returnCode;
    }
    public String getDescription() {
        return description;
    }
    public Object getResult() {
        return result;
    }

    public static R ok() {
        return new R();
    }

    public static R ok(String description) {
        R r = new R();
        r.description = description;
        return r;
    }

    public static R ok(Object result) {
        R r = new R();
        r.result = result;
        return r;
    }
    
    public static R error() {
        R r = new R();
        r.returnCode = -1;
        r.description = "未知异常,请联系管理员";
        return r;
    }
    
    public static R error(int returnCode, String description) {
        R r = new R();
        r.returnCode = returnCode;
        r.description = description;
        return r;
    }

}
R.java

 

posted @ 2021-03-21 15:06  且行且码  阅读(323)  评论(0编辑  收藏  举报