基于JavaConfig实现Spring5集成MyBatis3
工程模块划分
目前在企业应用开发中为了能够更加方便开发、测试和部署,通常都会根据不同的业务以及功能来把项目划分为多个模块,这里在实现Spring5集成MyBaits3之前先划分下项目模块,模块名称及其说明如下所示
模块名称 | 模块说明 |
---|---|
mybatis-practices-pojo | 存放项目开发中的各种POJO对象 |
mybatis-practices-utils | 存放项目开发中的各种第三方工具类 |
mybatis-practices-spring5-integration | 基于javaconfig实现Spring5集成Mybatis3 |
mybatis-practices-common | 存放项目中常用的通用工具 |
项目依赖说明
mybatis-practices-spring5-integration模块依赖总览图如下所示
主要包含了mybatis-practices-pojo,mybatis-practices-utils,mybatis-practices-common三个基础模块,mybatis,spring集成相关依赖以及常用工具包的依赖,详细的依赖说明可以参考mybatis-practices的pom.xml文件中的描述。
集成Log4j2完成日志记录
首先添加Log4j2的依赖,pom.xml文件内容如下
<properties>
<log4j2.version>2.9.0</log4j2.version>
</properties>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4j2.version}</version>
</dependency>
然后在模块mybatis-practices-spring5-integration的src/main/resources目录下添加log4j2的配置文件log4j2.xml,实现如下
<?xml version="1.0" encoding="UTF-8"?>
<!--设置log4j2的自身log级别为warn -->
<configuration status="warn" monitorInterval="3600" shutdownHook="disable">
<properties>
<Property name="app_name">mybatis-practices-spring5-integration</Property>
<Property name="log_path">logs/${app_name}</Property>
<Property name="log_file_size">100MB</Property>
</properties>
<appenders>
<!--
Console 的target是SYSTEM_OUT是输出到统一的输出流,没有指定日志文件
配置文件log4j2.xml 中的<Console name="Console" target="SYSTEM_OUT">表示 log4j2将日志配置到System.out输入到控制到输出流。
-->
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss SSS}][%t][%p][%l] %m%n" />
</console>
<!--info级别的日志记录配置-->
<RollingFile name="RollingFileInfo" fileName="${log_path}/info.log"
filePattern="${log_path}/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="INFO" />
<ThresholdFilter level="WARN" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="[%d%d{yyyy-MM-dd HH:mm:ss SSS}][%t][%p][%c:%L] %m%n" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${log_file_size}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="0" max="10"/>
</RollingFile>
<!--warn级别的日志记录配置-->
<RollingFile name="RollingFileWarn" fileName="${log_path}/warn.log"
filePattern="${log_path}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<ThresholdFilter level="WARN" />
<ThresholdFilter level="ERROR" onMatch="DENY"
onMismatch="NEUTRAL" />
</Filters>
<PatternLayout pattern="[%d%d{yyyy-MM-dd HH:mm:ss SSS}][%t][%p][%c:%L] %m%n" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${log_file_size}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="0" max="10"/>
</RollingFile>
<!-- error级别的日志配置-->
<RollingFile name="RollingFileError" fileName="${log_path}/error.log"
filePattern="${log_path}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="ERROR" />
<PatternLayout pattern="[%d%d{yyyy-MM-dd HH:mm:ss SSS}][%t][%p][%c:%L] %m%n" />
<Policies>
<!-- 归档每天的文件 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<!-- 限制单个文件大小 -->
<SizeBasedTriggeringPolicy size="${log_file_size}" />
</Policies>
<!-- 限制每天文件个数 -->
<DefaultRolloverStrategy compressionLevel="0" max="10"/>
</RollingFile>
</appenders>
<loggers>
<!--第三方框架显示的日志级别-->
<logger name="org.springframework" level="INFO"/>
<logger name ="org.ibatis" level="info"/>
<root level="info">
<appender-ref ref="Console" />
<appender-ref ref="RollingFileInfo" />
<appender-ref ref="RollingFileWarn" />
<appender-ref ref="RollingFileError" />
</root>
</loggers>
</configuration>
程序中应用
//类中实例化Logger对象
private static final Logger LOGGER= LogManager.getLogger();
//方法中调用对应的方法
LOGGER.info("");
LOGGER.error("");
集成P6Spy显示程序运行时的真实SQL
配置说明:http://p6spy.readthedocs.io/en/latest/configandusage.html
首先添加P6Spy和阿里巴巴数据库连接池Druid的依赖,pom.xml文件的内容如下
<properties>
<p6spy.version>3.6.0</p6spy.version>
<druid.version>1.1.6</druid.version>
</properties>
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>${p6spy.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
然后将p6spy的配置文件spy.properties放置项目的src/main/resources目录下,该文件只需要修改logfile,logMessageFormat,dateformat属性即可,如下图所示:
logfile=mybatis-practices-core.log
logMessageFormat= net.ittimeline.mybatis.practices.common.p6spy.CustomizeLineFormat
dateformat=yyyy-MM-dd HH:mm:ss
完整的spy.properties文件已经在mybatis-practice-spring5-integration模块中给出,仅供参考。
然后实现自定义的SQL输出格式
为了输出的内容足够的简洁,这里只保留了当前时间,执行SQL的耗时以及执行的SQL语句,具体实现如下所示
package net.ittimeline.mybatis.practices.common.p6spy;
import com.alibaba.druid.sql.SQLUtils;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
/**
* 自定义SQL格式
* @author tony ittimeline@163.com
* @date 2018-01-30-下午10:45
* @website wwww.ittimeline.net
* @see
* @since JDK8u162
*/
public class CustomizeLineFormat implements MessageFormattingStrategy {
public String buildMessage(String now, long elapsed, String sql) {
StringBuffer content = new StringBuffer();
if (org.apache.commons.lang3.StringUtils.isNotEmpty(now) && org.apache.commons.lang3.StringUtils.isNotEmpty(Long.valueOf(elapsed).toString())
&& org.apache.commons.lang3.StringUtils.isNotEmpty(sql)) {
content.append("当前时间:" + now);
content.append(" SQL执行耗时(毫秒)为" + elapsed);
content.append(" SQL执行的语句是\n" + SQLUtils.formatMySql(sql)+"\n\n");
}
return content.toString();
}
@Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql) {
return buildMessage(now, elapsed, sql);
}
}
然后增加一个database.properties文件,配置内容如下
相对于未添加P6Spy之前,数据库的驱动类和URL不同,同时该文件还配置了阿里巴巴数据库连接池Druid的相关属性
#############数据库连接池druid配置########
datasource.druid.driverClassName=com.p6spy.engine.spy.P6SpyDriver
datasource.druid.url=jdbc:p6spy:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=UTF-8&useSSL=false
datasource.druid.username=root
datasource.druid.password=guanglei
datasource.druid.initialSize=1
datasource.druid.minIdle=1
datasource.druid.maxActive=20
datasource.druid.maxWait=60000
datasource.druid.timeBetweenEvictionRunsMillis=60000
datasource.druid.minEvictableIdleTimeMillis=300000
datasource.druid.validationQuery=SELECT 'x'
datasource.druid.testWhileIdle=true
datasource.druid.testOnBorrow=false
datasource.druid.testOnReturn=false
datasource.druid.poolPreparedStatements=false
datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
datasource.druid.filters=stat
集成阿里巴巴Druid数据库连接池
项目地址:https://github.com/alibaba/druid
完整实现如下所示
package net.ittimeline.mybatis.practices.spring5.integration.configuration;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
import java.sql.SQLException;
/**
* 阿里巴巴数据库连接池Druid配置
* @author tony ittimeline@163.com
* @date 2018-02-26-上午11:30
* @website wwww.ittimeline.net
* @see
* @since JDK8u162
*/
@Configuration
@PropertySource("classpath:database.properties")
public class DruidDataSourceConfiguraiton {
private static final Logger LOGGER= LogManager.getLogger();
/**********************读取位于ClassPath路径下的database.properties属性配置文件,然后将属性值设置到对应的成员变量中********************/
@Value("${datasource.druid.driverClassName}")
private String driverClassName;
@Value("${datasource.druid.url}")
private String url;
@Value("${datasource.druid.username}")
private String userName;
@Value("${datasource.druid.password}")
private String password;
@Value("${datasource.druid.initialSize}")
private int initialSize;
@Value("${datasource.druid.minIdle}")
private int minIdle;
@Value("${datasource.druid.maxActive}")
private int maxActive;
@Value("${datasource.druid.maxWait}")
private long maxWait;
@Value("${datasource.druid.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
@Value("${datasource.druid.minEvictableIdleTimeMillis}")
private long minEvictableIdleTimeMillis;
@Value("${datasource.druid.validationQuery}")
private String validationQuery;
@Value("${datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${datasource.druid.testOnReturn}")
private boolean testOnReturn;
@Value("${datasource.druid.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${datasource.druid.maxPoolPreparedStatementPerConnectionSize}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${datasource.druid.filters}")
private String filters;
/**
* 基于阿里巴巴的Druid数据库连接池实现
* @return
*/
@Bean
public DataSource dataSource(){
DruidDataSource dataSource =new DruidDataSource();
dataSource.setUsername(userName);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driverClassName);
dataSource.setInitialSize(initialSize);
dataSource.setMinIdle(minIdle);
dataSource.setMaxActive(maxActive);
dataSource.setMaxWait(maxWait);
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
dataSource.setValidationQuery(validationQuery);
dataSource.setTestWhileIdle(testWhileIdle);
dataSource.setTestOnBorrow(testOnBorrow);
dataSource.setTestOnReturn(testOnReturn);
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
try {
dataSource.setFilters(filters);
} catch (SQLException e) {
e.printStackTrace();
LOGGER.info("build datasource fail "+e.getMessage());
}
return dataSource;
}
}
其中@PropertySource注解用于读取类路径下的数据库配置文件database.properties,同时使用@Value注解结合Spring表达式赋值于对应的成员变量。
@Bean注解的作用就是相当于传统spring的xml配置的<bean/>
基于JavaConfig的MyBatis3集成Spring5
系统全局配置类
目前只配置了包扫描器
package net.ittimeline.mybatis.practices.spring5.integration.configuration;
import net.ittimeline.mybatis.practices.spring5.integration.constants.SystemConstants;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 系统全局配置类
* @author tony ittimeline@163.com
* @date 2018-02-24-上午11:03
* @website wwww.ittimeline.net
* @see
* @since JDK8u162
*/
@Configuration
@ComponentScan(basePackages = {SystemConstants.COMPONENT_SCAN_PACKAGE_NAME})
public class ApplicationConfiguartion {
}
系统常量类
package net.ittimeline.mybatis.practices.spring5.integration.constants;
/**
* 系统常量配置
* @author tony ittimeline@163.com
* @date 2018-02-24-上午11:09
* @website wwww.ittimeline.net
* @see
* @since JDK8u162
*/
public class SystemConstants {
public static final String COMPONENT_SCAN_PACKAGE_NAME="net.ittimeline.mybatis.practices.spring5";
public static final String MAPPER_BASE_PACKAGE="net.ittimeline.mybatis.practices.spring5.integration.mapper";
}
MyBatis3集成spring5的核心配置类
package net.ittimeline.mybatis.practices.spring5.integration.configuration;
import net.ittimeline.mybatis.practices.spring5.integration.constants.SystemConstants;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
/**
* @author tony ittimeline@163.com
* @date 2018-02-24-上午11:13
* @website wwww.ittimeline.net
* @see
* @since JDK8u162
*/
@Configuration
@Import(DruidDataSourceConfiguraiton.class)
@MapperScan(basePackages = SystemConstants.MAPPER_BASE_PACKAGE)
public class MyBatisConfiguartion {
private static final Logger LOGGER= LogManager.getLogger();
private static final String TYPE_ALIASES_PACKAGE= "net.ittimeline.mybatis.practices.pojo.persist";
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setTypeAliasesPackage(TYPE_ALIASES_PACKAGE);
//TODO 添加分页插件
//添加mapper的XML目录
ResourcePatternResolver resourcePatternResolver=new PathMatchingResourcePatternResolver();
try {
sqlSessionFactoryBean.setMapperLocations(resourcePatternResolver.getResources("classpath:mappers/*.xml"));
return sqlSessionFactoryBean.getObject();
} catch (Exception e) {
e.printStackTrace();
LOGGER.info("build sqlSessionFactory fail "+e.getMessage());
throw new RuntimeException(e);
}
}
/**
*
* @param sqlSessionFactory
* @return
*/
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
基于JavaConfig的Spring事务配置
package net.ittimeline.mybatis.practices.spring5.integration.configuration;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RollbackRuleAttribute;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Spring事务配置
* @author tony ittimeline@163.com
* @date 2018-02-26-下午4:27
* @website wwww.ittimeline.net
* @see
* @since JDK8u162
*/
@Configuration
@Import(DruidDataSourceConfiguraiton.class)
public class SpringTransactionConfiguration {
/**
* 自定义事务拦截名称
*/
private static final String CUSTOMIZE_TRANSACTION_INTERCEPTOR_NAME="customizeTransactionInterceptor";
/**
* 默认只对*Service和*ServiceImpl Bean进行事务处理,"*"表示模糊匹配,比如userService,userServiceImpl
*/
private static final String[] DEFAULT_TRANSACTION_BEAN_NAMES={"*Service","*ServiceImpl"};
/**
* 可传播事务配置
*/
private static final String[] DEFAULT_REQUIRED_METHOD_RULE_TRANSACTION_ATTRIBUTES={"add","save","insert","delete","update","edit","batch","create","remove"};
/**
* 默认的只读事务
*/
private static final String[] DEFAULT_READ_ONLY_METHOD_RULE_TRANSACTION_ATTRIBUTES={"get","count","find","query","select","list","*"};
/**
* 自定义事务 BeanName 拦截
*/
private String[] customizeTransactionBeanNames = {};
/**
* 自定义方法名的事务属性相关联,可以使用通配符(*)字符关联相同的事务属性的设置方法; 只读事务
*/
private String[] customizeReadOnlyMethodRuleTransactionAttributes = {};
/**
* 自定义方法名的事务属性相关联,可以使用通配符(*)字符关联相同的事务属性的设置方法;
* 传播事务(默认的){@link org.springframework.transaction.annotation.Propagation#REQUIRED}
*/
private String[] customizeRequiredMethodRuleTransactionAttributes = {};
/**
* 事务管理器
* @param dataSource
* @return
*/
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
/**
* 配置事务拦截器
*
* @param transactionManager : 事务管理器
*/
@Bean( CUSTOMIZE_TRANSACTION_INTERCEPTOR_NAME )
public TransactionInterceptor customizeTransactionInterceptor ( PlatformTransactionManager transactionManager ) {
NameMatchTransactionAttributeSource transactionAttributeSource = new NameMatchTransactionAttributeSource();
RuleBasedTransactionAttribute readOnly = this.readOnlyTransactionRule();
RuleBasedTransactionAttribute required = this.requiredTransactionRule();
// 默认的只读事务配置
for ( String methodName : DEFAULT_READ_ONLY_METHOD_RULE_TRANSACTION_ATTRIBUTES ) {
transactionAttributeSource.addTransactionalMethod( methodName , readOnly );
}
// 默认的传播事务配置
for ( String methodName : DEFAULT_REQUIRED_METHOD_RULE_TRANSACTION_ATTRIBUTES ) {
transactionAttributeSource.addTransactionalMethod( methodName , required );
}
// 定制的只读事务配置
for ( String methodName : customizeReadOnlyMethodRuleTransactionAttributes ) {
transactionAttributeSource.addTransactionalMethod( methodName , readOnly );
}
// 定制的传播事务配置
for ( String methodName : customizeRequiredMethodRuleTransactionAttributes ) {
transactionAttributeSource.addTransactionalMethod( methodName , required );
}
return new TransactionInterceptor( transactionManager , transactionAttributeSource );
}
/**
* 配置事务拦截
* <p>
* {@link #customizeTransactionInterceptor(PlatformTransactionManager)}
*/
@Bean
public BeanNameAutoProxyCreator customizeTransactionBeanNameAutoProxyCreator () {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
// 设置定制的事务拦截器
beanNameAutoProxyCreator.setInterceptorNames( CUSTOMIZE_TRANSACTION_INTERCEPTOR_NAME );
List< String > transactionBeanNames = new ArrayList<>( DEFAULT_TRANSACTION_BEAN_NAMES.length + customizeTransactionBeanNames.length );
// 默认
transactionBeanNames.addAll( Arrays.asList( DEFAULT_TRANSACTION_BEAN_NAMES ) );
// 定制
transactionBeanNames.addAll( Arrays.asList( customizeTransactionBeanNames ) );
// 归集
for ( String transactionBeanName : transactionBeanNames ) {
beanNameAutoProxyCreator.setBeanNames( transactionBeanName );
}
beanNameAutoProxyCreator.setProxyTargetClass( true );
return beanNameAutoProxyCreator;
}
/**
* 支持当前事务;如果不存在创建一个新的
* {@link org.springframework.transaction.annotation.Propagation#REQUIRED}
*/
private RuleBasedTransactionAttribute requiredTransactionRule () {
RuleBasedTransactionAttribute required = new RuleBasedTransactionAttribute();
required.setRollbackRules( Collections.singletonList( new RollbackRuleAttribute( Exception.class ) ) );
required.setPropagationBehavior( TransactionDefinition.PROPAGATION_REQUIRED );
required.setTimeout( TransactionDefinition.TIMEOUT_DEFAULT );
return required;
}
/**
* 只读事务
* {@link org.springframework.transaction.annotation.Propagation#NOT_SUPPORTED}
*/
private RuleBasedTransactionAttribute readOnlyTransactionRule () {
RuleBasedTransactionAttribute readOnly = new RuleBasedTransactionAttribute();
readOnly.setReadOnly( true );
readOnly.setPropagationBehavior( TransactionDefinition.PROPAGATION_NOT_SUPPORTED );
return readOnly;
}
}