spring boot学习3之mybatis+druid+事务+PageHelper分页插件+sql打印插件整合
2017-05-20 14:35
597 查看
上篇博文学习了spring boot下对于配置文件的读取。这篇博文学习下spring boot怎样整合mybatis,并使用指定的数据库连接池,事务,分页插件的整合。
该例子配置开启事务,所以先说下事务的一点注意事项。
注:虽然在配置类中开启了事务,但是没有指定哪些类哪些方法的执行要开启事务(除非定义个AOP),所以需要自行在service类上面或方法上面@Transactional。在类上面加@Transactional,说明类内的每个public方法都默认使用它,除非方法上自行定义@Transactional覆盖类上的定义。
如果类或方法名上都没有@Transactional,那么执行了数据库操作后,后续代码抛出了异常,数据库也不会回滚,因为没声明开启事务,数据库默认自动提交了。
事务嵌套:@Transactional时,默认Propagation.REQUIRED (有事务就处于当前事务中,没事务就创建一个事务),所以如果A01Service.a()调用A01Service.b()同时调用A02Service.c(),同类内部的事务方法或不同类的事务方法,spring处理时,会将它们都置于同一个事务中,也就是要么都失败,要么都成功,不会出现多个事务,一个提交了,另一个回滚了。这个进行框架搭建时,可自行测试。
本例子用的是maven结构。例子源码下载在github上,点击下载
项目结构
mapper接口和mapper.xml文件,我都是放在dao文件夹下
其中pom.xml文件
application.yml文件
logging:
config: classpath:logback.xml
path: d:/logs
server:
port: 80
session-timeout: 60
mybatis:
mapperLocations: classpath:/com/fei/springboot/dao/*.xml
typeAliasesPackage: com.fei.springboot.dao
mapperScanPackage: com.fei.springboot.dao
configLocation: classpath:/mybatis-config.xml
spring:
datasource:
name: db
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
minIdle: 5
maxActive: 100
initialSize: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 50
removeAbandoned: true
filters: stat # ,wall,log4j # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据
druidLoginName: wjf # 登录druid的账号
druidPassword: wjf # 登录druid的密码
mybatis-config.xml
由于数据库连接池用的是druid,所以需要自己写下代码
DatasourceConfig.java代码
package com.fei.springboot.mybatisconfig;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
@Configuration
public class DatasourceConfig {
private Logger logger = LoggerFactory.getLogger(DatasourceConfig.class);
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.type}")
private String dbType;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Value("${spring.datasource.useGlobalDataSourceStat}")
private boolean useGlobalDataSourceStat;
@Value("${spring.datasource.druidLoginName}")
private String druidLoginName;
@Value("${spring.datasource.druidPassword}")
private String druidPassword;
@Bean(name="dataSource",destroyMethod = "close", initMethod="init")
@Primary //不要漏了这
public DataSource dataSource(){
DruidDataSource datasource = new DruidDataSource();
try {
datasource.setUrl(this.dbUrl);
datasource.setDbType(dbType);
datasource.setUsername(username);
datasource.setPassword(password);
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.setFilters(filters);
datasource.setConnectionProperties(connectionProperties);
datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
} catch (SQLException e) {
logger.error("druid configuration initialization filter", e);
}
return datasource;
}
///////// 下面是druid 监控访问的设置 /////////////////
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*"); //url 匹配
reg.addInitParameter("allow", "192.168.16.110,127.0.0.1"); // IP白名单 (没有配置或者为空,则允许所有访问)
reg.addInitParameter("deny", "192.168.16.111"); //IP黑名单 (存在共同时,deny优先于allow)
reg.addInitParameter("loginUsername", this.druidLoginName);//登录名
reg.addInitParameter("loginPassword", this.druidPassword);//登录密码
reg.addInitParameter("resetEnable", "false"); // 禁用HTML页面上的“Reset All”功能
return reg;
}
@Bean(name="druidWebStatFilter")
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //忽略资源
filterRegistrationBean.addInitParameter("profileEnable", "true");
filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
return filterRegistrationBean;
}
}
MybatisConfiguration.java (注:PageHelper不要使用5.0以上版本,因为5.0以上没实现Interceptor接口,导致SqlSessionFactoryBean添加插件时,失败)
SqlPrintInterceptor.java
package com.fei.springboot.mybatisconfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
/**
* MyBatis 将mybatis要执行的sql拦截打印出来
*
* @since 1.0.0
*/
@Intercepts
({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlPrintInterceptor implements Interceptor {
private static Log logger = LogFactory.getLog(SqlPrintInterceptor.class);
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameterObject = null;
if (invocation.getArgs().length > 1) {
parameterObject = invocation.getArgs()[1];
}
long start = System.currentTimeMillis();
Object result = invocation.proceed();
String statementId = mappedStatement.getId();
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
Configuration configuration = mappedStatement.getConfiguration();
String sql = getSql(boundSql, parameterObject, configuration);
long end = System.currentTimeMillis();
long timing = end - start;
if(logger.isInfoEnabled()){
logger.info("执行sql耗时:" + timing + " ms" + " - id:" + statementId + " - Sql:" );
logger.info(" "+sql);
}
return result;
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) {
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
sql = replacePlaceholder(sql, value);
}
}
}
return sql;
}
private String replacePlaceholder(String sql, Object propertyValue) {
String result;
if (propertyValue != null) {
if (propertyValue instanceof String) {
result = "'" + propertyValue + "'";
} else if (propertyValue instanceof Date) {
result = "'" + DATE_FORMAT.format(propertyValue) + "'";
} else {
result = propertyValue.toString();
}
} else {
result = "null";
}
return sql.replaceFirst("\\?", Matcher.quoteReplacement(result));
}
}
按照项目开发习惯,写个dao/service/controller来进行测试下
UserMapper.java
UserMapper.xml
注:虽然在配置类中开启了事务,但是没有指定哪些类哪些方法的执行要开启事务,所以需要自行在service类上面或方法上面@Transactional。在类上面加@Transactional,说明类内的每个public方法都默认使用它,除非方法上自行定义@Transactional覆盖类上的定义。
如果类或方法名上都没有@Transactional,那么执行了数据库操作后,后续代码抛出了异常,数据库也不会回滚,因为没声明开启事务,数据库默认自动提交了。
事务嵌套:@Transactional时,默认Propagation.REQUIRED (有事务就处于当前事务中,没事务就创建一个事务),所以如果A01Service.a()调用A01Service.b()同时调用A02Service.c(),同类内部的事务方法或不同类的事务方法,spring处理时,会将它们都置于同一个事务中,也就是要么都失败,要么都成功,不会出现多个事务,一个提交了,另一个回滚了。这个进行框架搭建时,可自行测试。
UserController.java
执行Application.java,然后浏览器访问controller里的url即可进行测试
比如访问查询时,可以看到日志中打印了sql,并且PageHelper也正常工作
习惯JPA的同学,发现insert对象都需要写sql,会觉得麻烦。那可以把通用Mapper整合进去。通用Mapper比较成熟了,它以整合了jpa,具体可以百度通用mapper,git上作者已经提供spring boot+mybatis+通用mapper的整合demo了
druid 监控
访问 http://127.0.0.1/druid/ 输入账号/密码(配置文件那配置的druidLoginName)
完整的例子源码下载
该例子配置开启事务,所以先说下事务的一点注意事项。
注:虽然在配置类中开启了事务,但是没有指定哪些类哪些方法的执行要开启事务(除非定义个AOP),所以需要自行在service类上面或方法上面@Transactional。在类上面加@Transactional,说明类内的每个public方法都默认使用它,除非方法上自行定义@Transactional覆盖类上的定义。
如果类或方法名上都没有@Transactional,那么执行了数据库操作后,后续代码抛出了异常,数据库也不会回滚,因为没声明开启事务,数据库默认自动提交了。
事务嵌套:@Transactional时,默认Propagation.REQUIRED (有事务就处于当前事务中,没事务就创建一个事务),所以如果A01Service.a()调用A01Service.b()同时调用A02Service.c(),同类内部的事务方法或不同类的事务方法,spring处理时,会将它们都置于同一个事务中,也就是要么都失败,要么都成功,不会出现多个事务,一个提交了,另一个回滚了。这个进行框架搭建时,可自行测试。
本例子用的是maven结构。例子源码下载在github上,点击下载
项目结构
mapper接口和mapper.xml文件,我都是放在dao文件夹下
其中pom.xml文件
<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.fei.springboot</groupId> <artifactId>springboot-mybatis</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <!-- 文件拷贝时的编码 --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <!-- 编译时的编码 --> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <spring_versin>1.5.2.RELEASE</spring_versin> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring_versin}</version> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>${spring_versin}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version> </dependency> <!-- 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>4.1.6</version> </dependency> </dependencies> <build> <sourceDirectory>src</sourceDirectory> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
application.yml文件
logging:
config: classpath:logback.xml
path: d:/logs
server:
port: 80
session-timeout: 60
mybatis:
mapperLocations: classpath:/com/fei/springboot/dao/*.xml
typeAliasesPackage: com.fei.springboot.dao
mapperScanPackage: com.fei.springboot.dao
configLocation: classpath:/mybatis-config.xml
spring:
datasource:
name: db
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
minIdle: 5
maxActive: 100
initialSize: 10
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 50
removeAbandoned: true
filters: stat # ,wall,log4j # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
useGlobalDataSourceStat: true # 合并多个DruidDataSource的监控数据
druidLoginName: wjf # 登录druid的账号
druidPassword: wjf # 登录druid的密码
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 使全局的映射器启用或禁用缓存。 --> <setting name="cacheEnabled" value="true" /> <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 --> <setting name="lazyLoadingEnabled" value="true" /> <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 --> <setting name="aggressiveLazyLoading" value="true"/> <!-- 是否允许单条sql 返回多个数据集 (取决于驱动的兼容性) default:true --> <setting name="multipleResultSetsEnabled" value="true" /> <!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true --> <setting name="useColumnLabel" value="true" /> <!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。 default:false --> <setting name="useGeneratedKeys" value="false" /> <!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分 FULL:全部 --> <setting name="autoMappingBehavior" value="PARTIAL" /> <!-- 这是默认的执行类型 (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新) --> <setting name="defaultExecutorType" value="SIMPLE" /> <setting name="defaultStatementTimeout" value="25" /> <setting name="defaultFetchSize" value="100" /> <setting name="safeRowBoundsEnabled" value="false" /> <!-- 使用驼峰命名法转换字段。 --> <setting name="mapUnderscoreToCamelCase" value="true" /> <!-- 设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session --> <setting name="localCacheScope" value="SESSION" /> <!-- 默认为OTHER,为了解决oracle插入null报错的问题要设置为NULL --> <setting name="jdbcTypeForNull" value="NULL" /> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" /> </settings> </configuration>配置文件大概就这样
由于数据库连接池用的是druid,所以需要自己写下代码
DatasourceConfig.java代码
package com.fei.springboot.mybatisconfig;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
@Configuration
public class DatasourceConfig {
private Logger logger = LoggerFactory.getLogger(DatasourceConfig.class);
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.type}")
private String dbType;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Value("${spring.datasource.useGlobalDataSourceStat}")
private boolean useGlobalDataSourceStat;
@Value("${spring.datasource.druidLoginName}")
private String druidLoginName;
@Value("${spring.datasource.druidPassword}")
private String druidPassword;
@Bean(name="dataSource",destroyMethod = "close", initMethod="init")
@Primary //不要漏了这
public DataSource dataSource(){
DruidDataSource datasource = new DruidDataSource();
try {
datasource.setUrl(this.dbUrl);
datasource.setDbType(dbType);
datasource.setUsername(username);
datasource.setPassword(password);
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.setFilters(filters);
datasource.setConnectionProperties(connectionProperties);
datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
} catch (SQLException e) {
logger.error("druid configuration initialization filter", e);
}
return datasource;
}
///////// 下面是druid 监控访问的设置 /////////////////
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*"); //url 匹配
reg.addInitParameter("allow", "192.168.16.110,127.0.0.1"); // IP白名单 (没有配置或者为空,则允许所有访问)
reg.addInitParameter("deny", "192.168.16.111"); //IP黑名单 (存在共同时,deny优先于allow)
reg.addInitParameter("loginUsername", this.druidLoginName);//登录名
reg.addInitParameter("loginPassword", this.druidPassword);//登录密码
reg.addInitParameter("resetEnable", "false"); // 禁用HTML页面上的“Reset All”功能
return reg;
}
@Bean(name="druidWebStatFilter")
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); //忽略资源
filterRegistrationBean.addInitParameter("profileEnable", "true");
filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE");
filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION");
return filterRegistrationBean;
}
}
MybatisConfiguration.java (注:PageHelper不要使用5.0以上版本,因为5.0以上没实现Interceptor接口,导致SqlSessionFactoryBean添加插件时,失败)
package com.fei.springboot.mybatisconfig; import java.io.IOException; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.TransactionManagementConfigurer; import com.github.pagehelper.PageHelper; /** * mybatis的相关配置设置 * @author Jfei * */ @Configuration @AutoConfigureAfter(DatasourceConfig.class) @ConfigurationProperties @EnableTransactionManagement //@MapperScan("com.fei.springboot.dao") public class MybatisConfiguration implements TransactionManagementConfigurer{ private static Log logger = LogFactory.getLog(MybatisConfiguration.class); // 配置类型别名 @Value("${mybatis.typeAliasesPackage}") private String typeAliasesPackage; // 配置mapper的扫描,找到所有的mapper.xml映射文件 // @Value("${mybatis.mapperLocations : classpath:com/fei/springboot/dao/*.xml}") @Value("${mybatis.mapperLocations}") private String mapperLocations; // 加载全局的配置文件 @Value("${mybatis.configLocation}") private String configLocation; @Autowired private DataSource dataSource; // 提供SqlSeesion @Bean(name = "sqlSessionFactory") @Primary public SqlSessionFactory sqlSessionFactory() { try { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); // 读取配置 sessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage); //设置mapper.xml文件所在位置 Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations); sessionFactoryBean.setMapperLocations(resources); //设置mybatis-config.xml配置文件位置 sessionFactoryBean.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); //添加分页插件、打印sql插件 Interceptor[] plugins = new Interceptor[]{pageHelper(),sqlPrintInterceptor()}; sessionFactoryBean.setPlugins(plugins); return sessionFactoryBean.getObject(); } catch (IOException e) { logger.error("mybatis resolver mapper*xml is error",e); return null; } catch (Exception e) { logger.error("mybatis sqlSessionFactoryBean create error",e); return null; } } @Bean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } //事务管理 @Bean public PlatformTransactionManager annotationDrivenTransactionManager() { return new DataSourceTransactionManager(dataSource); } //将要执行的sql进行日志打印(不想拦截,就把这方法注释掉) @Bean public SqlPrintInterceptor sqlPrintInterceptor(){ return new SqlPrintInterceptor(); } /** * 分页插件 * @param dataSource * @return */ // <!-- 分页插件 --> // <plugins> // <plugin interceptor="com.github.pagehelper.PageHelper"> // <property name="dialect" value="mysql"/> // <!-- 该参数默认为false --> // <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 --> // <!-- 和startPage中的pageNum效果一样 --> // <property name="offsetAsPageNum" value="true"/> // <!-- 该参数默认为false --> // <!-- 设置为true时,使用RowBounds分页会进行count查询 --> // <property name="rowBoundsWithCount" value="true"/> // <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 --> // <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) --> // <property name="pageSizeZero" value="true"/> // <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 --> // <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 --> // <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 --> // <property name="reasonable" value="false"/> // <!-- 支持通过Mapper接口参数来传递分页参数 --> // <property name="supportMethodsArguments" value="false"/> // <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page --> // <property name="returnPageInfo" value="none"/> // // </plugin> // </plugins> @Bean public PageHelper pageHelper() { PageHelper pageHelper = new PageHelper(); Properties p = new Properties(); p.setProperty("offsetAsPageNum", "true"); p.setProperty("rowBoundsWithCount", "true"); p.setProperty("reasonable", "true"); p.setProperty("returnPageInfo", "check"); p.setProperty("params", "count=countSql"); pageHelper.setProperties(p); return pageHelper; } }打印sql拦截插件
SqlPrintInterceptor.java
package com.fei.springboot.mybatisconfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
/**
* MyBatis 将mybatis要执行的sql拦截打印出来
*
* @since 1.0.0
*/
@Intercepts
({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlPrintInterceptor implements Interceptor {
private static Log logger = LogFactory.getLog(SqlPrintInterceptor.class);
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameterObject = null;
if (invocation.getArgs().length > 1) {
parameterObject = invocation.getArgs()[1];
}
long start = System.currentTimeMillis();
Object result = invocation.proceed();
String statementId = mappedStatement.getId();
BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
Configuration configuration = mappedStatement.getConfiguration();
String sql = getSql(boundSql, parameterObject, configuration);
long end = System.currentTimeMillis();
long timing = end - start;
if(logger.isInfoEnabled()){
logger.info("执行sql耗时:" + timing + " ms" + " - id:" + statementId + " - Sql:" );
logger.info(" "+sql);
}
return result;
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
private String getSql(BoundSql boundSql, Object parameterObject, Configuration configuration) {
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
sql = replacePlaceholder(sql, value);
}
}
}
return sql;
}
private String replacePlaceholder(String sql, Object propertyValue) {
String result;
if (propertyValue != null) {
if (propertyValue instanceof String) {
result = "'" + propertyValue + "'";
} else if (propertyValue instanceof Date) {
result = "'" + DATE_FORMAT.format(propertyValue) + "'";
} else {
result = propertyValue.toString();
}
} else {
result = "null";
}
return sql.replaceFirst("\\?", Matcher.quoteReplacement(result));
}
}
按照项目开发习惯,写个dao/service/controller来进行测试下
UserMapper.java
package com.fei.springboot.dao; import java.util.List; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import com.fei.springboot.domain.User; @Mapper public interface UserMapper { @Insert("insert sys_user(id,user_name) values(#{id},#{userName})") void insert(User u); //注:方法名和要UserMapper.xml中的id一致 List<User> query(@Param("userName")String userName); @Delete("delete from sys_user") void deleteAll(); }
UserMapper.xml
<?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.fei.springboot.dao.UserMapper"> <select id="query" resultType="com.fei.springboot.domain.User"> select id ,user_name from sys_user where 1=1 <if test="userName != null"> and user_name like CONCAT('%',#{userName},'%') </if> </select> </mapper>UserService.java
package com.fei.springboot.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.fei.springboot.dao.UserMapper; import com.fei.springboot.domain.User; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; @Service @Transactional(readOnly=true,rollbackFor=Exception.class) public class UserService { @Autowired private UserMapper userMapper; //注意:方法的@Transactional会覆盖类上面声明的事务 //Propagation.REQUIRED :有事务就处于当前事务中,没事务就创建一个事务 //isolation=Isolation.DEFAULT:事务数据库的默认隔离级别 @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false) public void insertUser(User u){ this.userMapper.insert(u); //如果类上面没有@Transactional,方法上也没有,哪怕throw new RuntimeException,数据库也会成功插入数据 // throw new RuntimeException("测试插入事务"); } public PageInfo<User> queryPage(String userName,int pageNum,int pageSize){ Page<User> page = PageHelper.startPage(pageNum, pageSize); //PageHelper会自动拦截到下面这查询sql this.userMapper.query(userName); return page.toPageInfo(); } //测试事务 //注意:方法的@Transactional会覆盖类上面声明的事务 //Propagation.REQUIRED :有事务就处于当前事务中,没事务就创建一个事务 //isolation=Isolation.DEFAULT:事务数据库的默认隔离级别 @Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false) public void testTransational(){ //删除全部 this.userMapper.deleteAll(); //新增 User u = new User(); u.setId("123456"); u.setUserName("张三"); this.userMapper.insert(u); //制造异常 //如果类上面没有@Transactional,方法上也没有,哪怕throw new RuntimeException,数据库也会提交 throw new RuntimeException("事务异常测试"); } }
注:虽然在配置类中开启了事务,但是没有指定哪些类哪些方法的执行要开启事务,所以需要自行在service类上面或方法上面@Transactional。在类上面加@Transactional,说明类内的每个public方法都默认使用它,除非方法上自行定义@Transactional覆盖类上的定义。
如果类或方法名上都没有@Transactional,那么执行了数据库操作后,后续代码抛出了异常,数据库也不会回滚,因为没声明开启事务,数据库默认自动提交了。
事务嵌套:@Transactional时,默认Propagation.REQUIRED (有事务就处于当前事务中,没事务就创建一个事务),所以如果A01Service.a()调用A01Service.b()同时调用A02Service.c(),同类内部的事务方法或不同类的事务方法,spring处理时,会将它们都置于同一个事务中,也就是要么都失败,要么都成功,不会出现多个事务,一个提交了,另一个回滚了。这个进行框架搭建时,可自行测试。
UserController.java
package com.fei.springboot.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.fei.springboot.domain.User; import com.fei.springboot.service.UserService; import com.github.pagehelper.PageInfo; @Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @RequestMapping("/hello") @ResponseBody public String hello(){ return "hello"; } /** * 测试插入 * @return */ @RequestMapping("/add") @ResponseBody public String add(){ User u = new User(); double i = Math.random()*100; u.setId(String.valueOf(i)); u.setUserName("test"+i); this.userService.insertUser(u); return "success"; } /** * 测试分页插件 * @return */ @RequestMapping("/queryPage") @ResponseBody public String queryPage(){ PageInfo<User> page = this.userService.queryPage("tes", 1, 2); System.out.println("总页数=" + page.getPages()); System.out.println("总记录数=" + page.getTotal()) ; for(User u : page.getList()){ System.out.println(u.getId() + " \t " + u.getUserName()); } return "success"; } /** * 测试事务 * @return */ @RequestMapping("/testTransational") @ResponseBody public String test(){ try { this.userService.testTransational(); return "success"; } catch (Exception e) { return e.getMessage(); } } }Application.java
package com.fei.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.boot.web.support.SpringBootServletInitializer; import org.springframework.context.annotation.ComponentScan; @EnableAutoConfiguration @ComponentScan(basePackages={"com.fei.springboot"}) @SpringBootApplication public class Application extends SpringBootServletInitializer implements EmbeddedServletContainerCustomizer{ @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(Application.class); } public static void main(String[] args) throws Exception { SpringApplication.run(Application.class, args); } public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer) { // configurableEmbeddedServletContainer.setPort(9090); } }
执行Application.java,然后浏览器访问controller里的url即可进行测试
比如访问查询时,可以看到日志中打印了sql,并且PageHelper也正常工作
2017-05-20 14:26:03.932 INFO 101936 --- [p-nio-80-exec-2] c.f.s.mybatisconfig.SqlPrintInterceptor : select id ,user_name from sys_user where 1=1 and user_name like CONCAT('%','tes','%') 2017-05-20 14:26:03.932 [http-nio-80-exec-2] INFO c.f.s.m.SqlPrintInterceptor - select id ,user_name from sys_user where 1=1 and user_name like CONCAT('%','tes','%') 总页数=3 总记录数=5 45.83897583806164 test45.83897583806164 5.580134425508676 test5.580134425508676
习惯JPA的同学,发现insert对象都需要写sql,会觉得麻烦。那可以把通用Mapper整合进去。通用Mapper比较成熟了,它以整合了jpa,具体可以百度通用mapper,git上作者已经提供spring boot+mybatis+通用mapper的整合demo了
druid 监控
访问 http://127.0.0.1/druid/ 输入账号/密码(配置文件那配置的druidLoginName)
完整的例子源码下载
相关文章推荐
- springboot整合mybatis将sql打印到日志(转)
- spring boot学习6之mybatis+PageHelper分页插件+jta多数据源事务整合
- springboot整合mybatis将sql打印到日志
- SpringBoot学习-mybaties SQL打印
- springboot整合mybatis将sql打印到日志
- springboot整合mybatis将sql打印到日志的实例详解
- Springboot整合mybatis将sql打印到日志
- springboot整合mybatis将sql打印到日志
- springboot学习笔记-6 springboot整合RabbitMQ
- spring boot学习系列:spring boot与jdbcTemplate的整合案例
- spring boot 学习笔记(3) 整合dubbo
- Spring-Boot学习(3)-----Spring-Boot整合mybatis
- Spring boot 和 mybatis 学习笔记3--动态sql
- Spring Boot学习总结(2)——Spring Boot整合Jsp
- Spring Boot学习记录(三)--整合Mybatis
- SpringBoot学习:整合shiro(身份认证和权限认证),使用EhCache缓存
- Spring-Boot学习笔记-整合Mybatis-Druid-PageHelper
- Spring Boot学习笔记 - 整合Swagger2自动生成RESTful API文档
- springboot使用之二:整合mybatis(xml方式)并添加PageHelper插件
- springboot学习笔记-2 一些常用的配置以及整合mybatis