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>
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()); } } } } }
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实体类对应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实体类对应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); } }
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; } }