《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!
org.springframework.boot
spring-boot-starter-jta-atomikos
org.projectlombok
lombok
1.16.10
mysql
mysql-connector-java
6.0.6
com.alibaba
druid-spring-boot-starter
1.1.9
org.springframework.boot
spring-boot-starter-aop
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.0
org.springframework.boot
spring-boot-starter-test
test
然后是数据源的yml信息,application.yml:
server:
port: 8077
spring:
application:
name: jta-dbsource
datasource:
druid:
mydbone:
url: jdbc:mysql://localhost:3306/mydbone?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&pinGlobalTxToPhysicalConnection=true&autoReconnect=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection 时
initialSize: 5
最小连接池数量
minIdle: 5
最大连接池数量
maxActive: 10
获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
maxWait: 60000
Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
timeBetweenEvictionRunsMillis: 60000
连接保持空闲而不被驱逐的最小时间
minEvictableIdleTimeMillis: 300000
用来检测连接是否有效的 sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
validationQuery: SELECT 1
建议配置为 true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery 检测连接是否有效。
testWhileIdle: true
申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnBorrow: false
归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false
是否自动回收超时连接
removeAbandoned: true
超时时间 (以秒数为单位)
remove-abandoned-timeout: 1800
logAbandoned: true
pinGlobalTxToPhysicalConnection: true
mydbtwo:
url: jdbc:mysql://localhost:3306/mydbtwo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&pinGlobalTxToPhysicalConnection=true&autoReconnect=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 6
minIdle: 6
maxActive: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
removeAbandoned: true
remove-abandoned-timeout: 1800
logAbandoned: true
pinGlobalTxToPhysicalConnection: true
WebStatFilter 用于采集 web-jdbc 关联监控的数据。
web-stat-filter:
是否开启 WebStatFilter 默认是 true
enabled: true
需要拦截的 url
url-pattern: /*
排除静态资源的请求
exclusions: “.js,.gif,.jpg,.png,.css,.ico,/druid/*”
Druid 内置提供了一个 StatViewServlet 用于展示 Druid 的统计信息。
stat-view-servlet:
#是否启用 StatViewServlet 默认值 true
enabled: true
需要拦截的 url
url-pattern: /druid/*
允许清空统计数据
reset-enable: true
login-username: myname
login-password: mypwd
-----接下来就是代码环节-----
========================
大家多注意看注释,很多关键信息都用注释方式进行了简明的介绍:
先创建一个自定义注解,DataSource.java:
import java.lang.annotation.*;
/**
-
@Author : JCccc
-
@CreateTime : 2019/8/28
-
@Description :
**/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default DataSourceNames.ONE;
}
然后是创建 DataSourceNames.java,用于简单数据源命名:
/**
-
@Author : JCccc
-
@CreateTime : 2019/8/28
-
@Description :
**/
public interface DataSourceNames {
String ONE = “ONE”;
String TWO = “TWO”;
}
ps:其实这些都是我之前aop切换数据源的时候敲的,大概8月份的时候,这次我相当于在这个基础上着重解决事务问题
然后是将自定义注解作为切点,进行aop方式动态切换逻辑补全,创建DynamicDataSourceAspect.java:
import com.test.jtadbsource.dbConfig.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
-
@Author : JCccc
-
@CreateTime : 2019/12/10
-
@Description :
**/
@Aspect
@Component
public class DynamicDataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
- 切点: 所有配置 DataSource 注解的方法
*/
@Pointcut(“@annotation(com.test.jtadbsource.dbAop.DataSource)”)
public void dataSourcePointCut() {}
@Around(“dataSourcePointCut()”)
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource ds = null;
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//获取自定义注解
ds = method.getAnnotation(DataSource.class);
if (ds == null) {
//如果监测到自定义注解不存在,那么默认切换到数据源 mydbone
DataSourceContextHolder.setDataSourceKey(DataSourceNames.ONE);
logger.info("set default datasource is " + DataSourceNames.ONE);
} else {
//自定义存在,则按照注解的值去切换数据源
DataSourceContextHolder.setDataSourceKey(ds.value());
logger.info("set datasource is " + ds.value());
}
return point.proceed();
}
@After(value = “dataSourcePointCut()”)
public void afterSwitchDS(JoinPoint point) {
DataSourceContextHolder.clearDataSourceKey();
logger.info(“clean datasource”);
}
}
上面用到的DataSourceContextHolder.java:
/**
-
@Author : JCccc
-
@CreateTime : 2019/12/10
-
@Description :
**/
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDataSourceKey(String dbName) {
contextHolder.set(dbName);
}
// 获取数据源名
public static String getDataSourceKey() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
ok,到这里,基本的动态切换边框的东西都完毕了,接下来是比较核心的:
1. DataSourceFactory.java :
用于 不同的数据源DataSource的信息配置,使用DruidXADataSource创建,支持jta事务;
将不同数据源DataSource分别都关联上对应的AtomikosDataSourceBean,这样事务能提取到JTA事务管理器;
重写数据源会话工厂,为每个数据源单独配置一个。
配置重写的sqlSessionTemplate,将实际使用的不同数据源的sqlsession和spring的事务机制关联起来。
import com.alibaba.druid.pool.xa.DruidXADataSource;
import com.test.jtadbsource.dbAop.DataSourceNames;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
-
@Author : JCccc
-
@CreateTime : 2019/12/10
-
@Description :多数据源配置
**/
@Configuration
@MapperScan(basePackages = DataSourceFactory.BASE_PACKAGES, sqlSessionTemplateRef = “sqlSessionTemplate”)
public class DataSourceFactory {
static final String BASE_PACKAGES = “com.test.jtadbsource.mapper”;
private static final String MAPPER_LOCATION = “classpath:mybatis/mapper/*.xml”;
/***
- 创建 DruidXADataSource mydbone 用@ConfigurationProperties 自动配置属性
*/
@Bean
@ConfigurationProperties(“spring.datasource.druid.mydbone”)
public DataSource druidDataSourceOne() {
return new DruidXADataSource();
}
/***
- 创建 DruidXADataSource mydbtwo
*/
@Bean
@ConfigurationProperties(“spring.datasource.druid.mydbtwo”)
public DataSource druidDataSourceTwo() {
return new DruidXADataSource();
}
/**
- 创建支持 XA 事务的 Atomikos 数据源 mydbone
*/
@Bean
public DataSource dataSourceOne(DataSource druidDataSourceOne) {
AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
sourceBean.setXaDataSource((DruidXADataSource) druidDataSourceOne);
// 必须为数据源指定唯一标识
sourceBean.setPoolSize(5);
sourceBean.setTestQuery(“SELECT 1”);
sourceBean.setUniqueResourceName(“mydbone”);
return sourceBean;
}
/**
- 创建支持 XA 事务的 Atomikos 数据源 mydbtwo
*/
@Bean
public DataSource dataSourceTwo(DataSource druidDataSourceTwo) {
AtomikosDataSourceBean sourceBean = new AtomikosDataSourceBean();
sourceBean.setXaDataSource((DruidXADataSource) druidDataSourceTwo);
sourceBean.setPoolSize(5);
sourceBean.setTestQuery(“SELECT 1”);
sourceBean.setUniqueResourceName(“mydbtwo”);
return sourceBean;
}
/**
-
@param dataSourceOne 数据源 mydbone
-
@return 数据源 mydbone 的会话工厂
*/
@Bean
public SqlSessionFactory sqlSessionFactoryOne(DataSource dataSourceOne)
throws Exception {
return createSqlSessionFactory(dataSourceOne);
}
/**
-
@param dataSourceTwo 数据源 mydbtwo
-
@return 数据源 mydbtwo 的会话工厂
*/
@Bean
public SqlSessionFactory sqlSessionFactoryTwo(DataSource dataSourceTwo)
throws Exception {
return createSqlSessionFactory(dataSourceTwo);
}
/***
-
sqlSessionTemplate 与 Spring 事务管理一起使用,以确保使用的实际 SqlSession 是与当前 Spring 事务关联的,
-
此外它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭,提交或回滚会话
-
@param sqlSessionFactoryOne 数据源 mydbone
-
@param sqlSessionFactoryTwo 数据源 mydbtwo
*/
@Bean
public CustomSqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactoryOne, SqlSessionFactory sqlSessionFactoryTwo) {
Map<Object, SqlSessionFactory> sqlSessionFactoryMap = new HashMap<>();
sqlSessionFactoryMap.put(DataSourceNames.ONE, sqlSessionFactoryOne);
sqlSessionFactoryMap.put(DataSourceNames.TWO, sqlSessionFactoryTwo);
CustomSqlSessionTemplate customSqlSessionTemplate = new CustomSqlSessionTemplate(sqlSessionFactoryOne);
customSqlSessionTemplate.setTargetSqlSessionFactories(sqlSessionFactoryMap);
return customSqlSessionTemplate;
}
/***
-
自定义会话工厂
-
@param dataSource 数据源
-
@return :自定义的会话工厂
*/
private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//配置驼峰命名
configuration.setMapUnderscoreToCamelCase(true);
//配置sql日志
configuration.setLogImpl(StdOutImpl.class);
factoryBean.setConfiguration(configuration);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//配置读取mapper.xml路径
factoryBean.setMapperLocations(resolver.getResources(MAPPER_LOCATION));
return factoryBean.getObject();
}
}
上面用到的自定义CustomSqlSessionTemplate (重写SqlSessionTemplate):
import static java.lang.reflect.Proxy.newProxyInstance;
import static org.apache.ibatis.reflection.ExceptionUtil.unwrapThrowable;
import static org.mybatis.spring.SqlSessionUtils.closeSqlSession;
import static org.mybatis.spring.SqlSessionUtils.getSqlSession;
import static org.mybatis.spring.SqlSessionUtils.isSqlSessionTransactional;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.util.Assert;
public class CustomSqlSessionTemplate extends SqlSessionTemplate {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
private Map<Object, SqlSessionFactory> targetSqlSessionFactories;
private SqlSessionFactory defaultTargetSqlSessionFactory;
/**
-
通过Map传入
-
@param targetSqlSessionFactories
*/
public void setTargetSqlSessionFactories(Map<Object, SqlSessionFactory> targetSqlSessionFactories) {
this.targetSqlSessionFactories = targetSqlSessionFactories;
}
public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
}
public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public CustomSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
.getEnvironment().getDataSource(), true));
}
public CustomSqlSessionTemplate(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());
this.defaultTargetSqlSessionFactory = sqlSessionFactory;
}
//通过DataSourceContextHolder获取当前的会话工厂
@Override
public SqlSessionFactory getSqlSessionFactory() {
String dataSourceKey = DataSourceContextHolder.getDataSourceKey();
SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactories.get(dataSourceKey);
if (targetSqlSessionFactory != null) {
return targetSqlSessionFactory;
} else if (defaultTargetSqlSessionFactory != null) {
return defaultTargetSqlSessionFactory;
} else {
Assert.notNull(targetSqlSessionFactories, “Property ‘targetSqlSessionFactories’ or ‘defaultTargetSqlSessionFactory’ are required”);
Assert.notNull(defaultTargetSqlSessionFactory, “Property ‘defaultTargetSqlSessionFactory’ or ‘targetSqlSessionFactories’ are required”);
}
return this.sqlSessionFactory;
}
@Override
public Configuration getConfiguration() {
return this.getSqlSessionFactory().getConfiguration();
}
public ExecutorType getExecutorType() {
return this.executorType;
}
public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
return this.exceptionTranslator;
}
/**
- {@inheritDoc}
*/
public T selectOne(String statement) {
return this.sqlSessionProxy. selectOne(statement);
}
/**
- {@inheritDoc}
*/
public T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy. selectOne(statement, parameter);
}
/**
- {@inheritDoc}
*/
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.sqlSessionProxy.<K, V> selectMap(statement, mapKey);
}
/**
- {@inheritDoc}
*/
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey);
}
/**
- {@inheritDoc}
*/
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return this.sqlSessionProxy.<K, V> selectMap(statement, parameter, mapKey, rowBounds);
}
/**
- {@inheritDoc}
*/
public List selectList(String statement) {
return this.sqlSessionProxy. selectList(statement);
}
/**
- {@inheritDoc}
*/
public List selectList(String statement, Object parameter) {
return this.sqlSessionProxy. selectList(statement, parameter);
}
/**
- {@inheritDoc}
*/
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.sqlSessionProxy. selectList(statement, parameter, rowBounds);
}
/**
- {@inheritDoc}
*/
public void select(String statement, ResultHandler handler) {
this.sqlSessionProxy.select(statement, handler);
}
/**
- {@inheritDoc}
*/
public void select(String statement, Object parameter, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, handler);
}
/**
- {@inheritDoc}
*/
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
}
/**
- {@inheritDoc}
*/
public int insert(String statement) {
return this.sqlSessionProxy.insert(statement);
}
/**
- {@inheritDoc}
*/
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}
/**
- {@inheritDoc}
*/
public int update(String statement) {
return this.sqlSessionProxy.update(statement);
}
/**
- {@inheritDoc}
*/
public int update(String statement, Object parameter) {
return this.sqlSessionProxy.update(statement, parameter);
}
/**
- {@inheritDoc}
*/
public int delete(String statement) {
return this.sqlSessionProxy.delete(statement);
}
/**
- {@inheritDoc}
*/
public int delete(String statement, Object parameter) {
return this.sqlSessionProxy.delete(statement, parameter);
}
/**
- {@inheritDoc}
*/
public T getMapper(Class type) {
return getConfiguration().getMapper(type, this);
}
/**
- {@inheritDoc}
*/
public void commit() {
throw new UnsupportedOperationException(“Manual commit is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
public void commit(boolean force) {
throw new UnsupportedOperationException(“Manual commit is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
public void rollback() {
throw new UnsupportedOperationException(“Manual rollback is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
public void rollback(boolean force) {
throw new UnsupportedOperationException(“Manual rollback is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
最后总结
搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析
最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!
ublic int delete(String statement, Object parameter) {
return this.sqlSessionProxy.delete(statement, parameter);
}
/**
- {@inheritDoc}
*/
public T getMapper(Class type) {
return getConfiguration().getMapper(type, this);
}
/**
- {@inheritDoc}
*/
public void commit() {
throw new UnsupportedOperationException(“Manual commit is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
public void commit(boolean force) {
throw new UnsupportedOperationException(“Manual commit is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
public void rollback() {
throw new UnsupportedOperationException(“Manual rollback is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
public void rollback(boolean force) {
throw new UnsupportedOperationException(“Manual rollback is not allowed over a Spring managed SqlSession”);
}
/**
- {@inheritDoc}
*/
最后总结
搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析
最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化
[外链图片转存中…(img-g3S8Imbc-1714754638231)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门,即可获取!