SpringBoot + MyBatis + Oracle 实现事务配置管理解析
SpringBoot 项目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.zzg</groupId>
<artifactId>migrate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.18.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<encoding>UTF-8</encoding>
<java.version>1.8</java.version>
<commons-lang.version>2.6</commons-lang.version>
<commons-codec.version>1.10</commons-codec.version>
<commons-lang3.version>3.9</commons-lang3.version>
<commons-net.version>3.6</commons-net.version>
<commons-io.version>2.6</commons-io.version>
<commons-collections.version>3.2.1</commons-collections.version>
<commons-text.version>1.8</commons-text.version>
<oracle.version>1.0</oracle.version>
<mybatis-spring-boot-starter.version>1.3.2</mybatis-spring-boot-starter.version>
<com.alibaba.druid.version>1.1.10</com.alibaba.druid.version>
</properties>
<dependencies>
<!--web 模块依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--web 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- apache common 模块 -->
<!--commons-lang3 工具包 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<!--commons-codec 加密工具包 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<!--commons-net 网络工具包 -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>${commons-net.version}</version>
</dependency>
<!--common-io 工具包 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<!--common-collection 工具包 -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<!-- common-text 工具包 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text.version}</version>
</dependency>
<!-- springboot整合mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
<!-- oracle驱动 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>${oracle.version}</version>
</dependency>
<!-- alibaba druid 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${com.alibaba.druid.version}</version>
</dependency>
<!-- PageHelp -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<!-- 集成Servlet-api 模块 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
SpringBoot 项目结构截图:
步骤一:在application.properties配置文件中指定oracle 数据库连接用户、密码、数据库和MyBatis 映射的*mapper.xml 文件
说明:mybatis.mapper-locations
所指向的位置是从src/main/resource开始的,前边需要加上classpath,它指向的是你的mapper.xml文件放置的位置。
application.properties
# oracle 数据库连接配置
spring.datasource.url=jdbc:oracle:thin:@192.168.1.150:1521:orcl
spring.datasource.username=erms_zhx
spring.datasource.password=erms_zhx
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# mybatis 配置
mybatis.mapper-locations=classpath*:out-mapper/*Mapper.xml
Spring Boot 如何完成Oracle 数据库初始化连接?
1、Spring Boot 项目启动时,实现DataSource及相关配置自动加载,主要通过@SpringBootApplication 注解中@EnableAutoConfiguration 属性。
2、SpringBoot 项目基于@EnableAutoConfiguration 属性,会调用org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration配置类
核心源码:
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {
}
@Configuration
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Tomcat.class, DataSourceConfiguration.Hikari.class,
DataSourceConfiguration.Dbcp.class, DataSourceConfiguration.Dbcp2.class,
DataSourceConfiguration.Generic.class })
@SuppressWarnings("deprecation")
protected static class PooledDataSourceConfiguration {
}
}
说明:DataSourceAutoConfiguration包含两个嵌套类,一个是EmbeddedDatabaseConfiguration(内嵌数据库),另一个是PooledDataSourceConfiguration(数据库连接池)。
EmbeddedDatabaseConfiguration表示已经嵌入Spring Boot的DataSource,仅仅需要在Maven中加入内嵌数据库的Driver,不做其他额外配置就能使用。从EmbeddedDatabaseType类可以看出,Spring Boot的内嵌DataSource支持HSQL,H2,DERBY这三种DB。
PooledDataSourceConfiguration表示Spring Boot还支持一些实现Pool的DataSource。从org.springframework.boot.jdbc.DataSourceBuilder中可以看出,当前版本的Spring Boot(2.0)只支持com.zaxxer.hikari.HikariDataSource,org.apache.tomcat.jdbc.pool.DataSource,org.apache.commons.dbcp2.BasicDataSource。其中,性能更加优秀的HikariDataSource是Spring Boot的默认选择。
3、SpringBoot 项目很少使用内嵌数据库进行数据存储,基于Spring Boot默认数据库连接池实现(HikariDataSource),继续讲解数据库连接池如何完成初始化。
继续调用PooledDataSourceConfiguration中org.springframework.boot.autoconfigure.jdbc.DataSourceConfiguration抽象类的Hikari嵌套类(DataSourceConfiguration抽象类的一个实现类)。
核心源码:
abstract class DataSourceConfiguration {
@SuppressWarnings("unchecked")
protected static <T> T createDataSource(DataSourceProperties properties,
Class<? extends DataSource> type) {
return (T) properties.initializeDataSourceBuilder().type(type).build();
}
/**
* Tomcat Pool DataSource configuration.
*/
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "org.apache.tomcat.jdbc.pool.DataSource", matchIfMissing = true)
static class Tomcat {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.tomcat")
public org.apache.tomcat.jdbc.pool.DataSource dataSource(
DataSourceProperties properties) {
org.apache.tomcat.jdbc.pool.DataSource dataSource = createDataSource(
properties, org.apache.tomcat.jdbc.pool.DataSource.class);
DatabaseDriver databaseDriver = DatabaseDriver
.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
dataSource.setTestOnBorrow(true);
dataSource.setValidationQuery(validationQuery);
}
return dataSource;
}
}
/**
* Hikari DataSource configuration.
*/
@ConditionalOnClass(HikariDataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", havingValue = "com.zaxxer.hikari.HikariDataSource", matchIfMissing = true)
static class Hikari {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public HikariDataSource dataSource(DataSourceProperties properties) {
return createDataSource(properties, HikariDataSource.class);
}
}
***省略其他代码片段***
}
说明:SpringBoot 默认使用HikariDataSource作为数据库连接池,HikariDataSource 对象实例化需要依赖两个配置对象:一个是org.springframework.boot.autoconfigure.jdbc.DataSourceProperties,另一个则是本类HikariDataSource的父类com.zaxxer.hikari.HikariConfig。
4、Spring Boot实例化HikariDataSource对象后,会通过HikariDataSource.getConnection()方法创建HikariPool对象。HikariPool及其父类PoolBase做了许多复杂的工作,包括创建Pool,创建Connection,读取Config,验证等等。调用HikariDataSource.getConnection()方法最终得到了这个Connection对象。这个过程中主要做了以下几步:
① 创建HikariPool对象。
② 调用HikariPool对象的父类对象PoolBase的构造器,读取HikariConfig配置信息配置PoolBase的属性。
③ 调用PoolBase的构造器的initializeDataSource方法,利用com.zaxxer.hikari.util.DriverDataSource创建DataSource对象(这里主要指JDBC URL方式)。DriverDataSource中会把所有的DataSource信息封装到driverProperties属性中,这是为了适配java.sql.Driver的connect(String url, java.util.Properties info)方法。
核心源代码:
package com.zaxxer.hikari.util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Enumeration;
import java.util.Map.Entry;
import java.util.Properties;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class DriverDataSource implements DataSource
{
private static final Logger LOGGER = LoggerFactory.getLogger(DriverDataSource.class);
private final String jdbcUrl;
private final Properties driverProperties;
private Driver driver;
public DriverDataSource(String jdbcUrl, String driverClassName, Properties properties, String username, String password)
{
this.jdbcUrl = jdbcUrl;
this.driverProperties = new Properties();
for (Entry<Object, Object> entry : properties.entrySet()) {
driverProperties.setProperty(entry.getKey().toString(), entry.getValue().toString());
}
if (username != null) {
driverProperties.put("user", driverProperties.getProperty("user", username));
}
if (password != null) {
driverProperties.put("password", driverProperties.getProperty("password", password));
}
if (driverClassName != null) {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver d = drivers.nextElement();
if (d.getClass().getName().equals(driverClassName)) {
driver = d;
break;
}
}
if (driver == null) {
LOGGER.warn("Registered driver with driverClassName={} was not found, trying direct instantiation.", driverClassName);
Class<?> driverClass = null;
ClassLoader threadContextClassLoader = Thread.currentThread().getContextClassLoader();
try {
if (threadContextClassLoader != null) {
try {
driverClass = threadContextClassLoader.loadClass(driverClassName);
LOGGER.debug("Driver class {} found in Thread context class loader {}", driverClassName, threadContextClassLoader);
}
catch (ClassNotFoundException e) {
LOGGER.debug("Driver class {} not found in Thread context class loader {}, trying classloader {}",
driverClassName, threadContextClassLoader, this.getClass().getClassLoader());
}
}
if (driverClass == null) {
driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
LOGGER.debug("Driver class {} found in the HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
}
} catch (ClassNotFoundException e) {
LOGGER.debug("Failed to load driver class {} from HikariConfig class classloader {}", driverClassName, this.getClass().getClassLoader());
}
if (driverClass != null) {
try {
driver = (Driver) driverClass.newInstance();
} catch (Exception e) {
LOGGER.warn("Failed to create instance of driver class {}, trying jdbcUrl resolution", driverClassName, e);
}
}
}
}
final String sanitizedUrl = jdbcUrl.replaceAll("([?&;]password=)[^&#;]*(.*)", "$1<masked>$2");
try {
if (driver == null) {
driver = DriverManager.getDriver(jdbcUrl);
LOGGER.debug("Loaded driver with class name {} for jdbcUrl={}", driver.getClass().getName(), sanitizedUrl);
}
else if (!driver.acceptsURL(jdbcUrl)) {
throw new RuntimeException("Driver " + driverClassName + " claims to not accept jdbcUrl, " + sanitizedUrl);
}
}
catch (SQLException e) {
throw new RuntimeException("Failed to get driver instance for jdbcUrl=" + sanitizedUrl, e);
}
}
@Override
public Connection getConnection() throws SQLException
{
return driver.connect(jdbcUrl, driverProperties);
}
@Override
public Connection getConnection(final String username, final String password) throws SQLException
{
final Properties cloned = (Properties) driverProperties.clone();
if (username != null) {
cloned.put("user", username);
if (cloned.containsKey("username")) {
cloned.put("username", username);
}
}
if (password != null) {
cloned.put("password", password);
}
return driver.connect(jdbcUrl, cloned);
}
***省略部分代码***
}
④ 调用HikariPool对象的构造器,同样也是配置一堆线程池信息。
⑤ 返回HikariPool.getConnection()。这个过程中,做了包含PoolBase.newPoolEntry()及PoolBase.newConnection()的许多复杂方法。从PoolBase.newConnection()可以看出,最终还是调用的步骤③的getConnection()方法获取到了这个Connection对象。
abstract class PoolBase
{
/***省略部分代码***/
/**
* Obtain connection from data source.
*
* @return a Connection connection
*/
private Connection newConnection() throws Exception
{
final long start = currentTime();
Connection connection = null;
try {
String username = config.getUsername();
String password = config.getPassword();
connection = (username == null) ? dataSource.getConnection() : dataSource.getConnection(username, password);
if (connection == null) {
throw new SQLTransientConnectionException("DataSource returned null unexpectedly");
}
setupConnection(connection);
lastConnectionFailure.set(null);
return connection;
}
catch (Exception e) {
if (connection != null) {
quietlyCloseConnection(connection, "(Failed to create/setup connection)");
}
else if (getLastConnectionFailure() == null) {
LOGGER.debug("{} - Failed to create/setup connection: {}", poolName, e.getMessage());
}
lastConnectionFailure.set(e);
throw e;
}
finally {
// tracker will be null during failFast check
if (metricsTracker != null) {
metricsTracker.recordConnectionCreated(elapsedMillis(start));
}
}
}
}
至此完成数据库连接初始化。
步骤二:在启动类处添加一个注解@MapperScan
说明:@MapperScan(basePackages = {"com.zzg.mapper"}) //扫描的是mapper.xml中namespace指向值的包位置
package com.zzg;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication//扫描同级包及子包
@EnableTransactionManagement //如果mybatis中service实现类中加入事务注解,需要此处添加该注解
@MapperScan(basePackages = {"com.zzg.mapper"})
public class MainApplication {
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(MainApplication.class, args);
}
}
步骤三:SpringBoot+MyBatis事务配置,等于在SpringBoot+MyBatis 配置基础上添加两个事务注解
事务配置依赖的注解:@EnableTransactionManagement 和@Transactional ,它们来源于spring-tx.jar 包
spring-tx.jar依赖于pom.xml 文件片段:
<!-- springboot整合mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
找到service实现类,加上@Transactional 注解,如果你加在类上,那该类所有的方法都会被事务管理,如果你加在方法上,那仅仅是该方法被事务管理。事务管理一般是加载拥有增、删、改方法才会需要事务。
@Override
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout=36000,rollbackFor=Exception.class)
public int insert(ErmsGuideProjBaseInfo record) {
// TODO Auto-generated method stub
return mapper.insert(record);
}
@Override
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout=36000,rollbackFor=Exception.class)
public int insertSelective(ErmsGuideProjBaseInfo record) {
// TODO Auto-generated method stub
return mapper.insertSelective(record);
}
@Override
public ErmsGuideProjBaseInfo selectByPrimaryKey(Long sid) {
// TODO Auto-generated method stub
return mapper.selectByPrimaryKey(sid);
}
@Override
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout=36000,rollbackFor=Exception.class)
public int updateByPrimaryKeySelective(ErmsGuideProjBaseInfo record) {
// TODO Auto-generated method stub
return mapper.updateByPrimaryKeySelective(record);
}
@Override
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout=36000,rollbackFor=Exception.class)
public int updateByPrimaryKey(ErmsGuideProjBaseInfo record) {
// TODO Auto-generated method stub
return mapper.updateByPrimaryKey(record);
}
spring boot启动类必须要开启事务,而开启事务用的注解就是@EnableTransactionManagement
package com.zzg;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication//扫描同级包及子包
@EnableTransactionManagement //如果mybatis中service实现类中加入事务注解,需要此处添加该注解
@MapperScan(basePackages = {"com.zzg.mapper"})
public class MainApplication {
public static void main(String[] args) {
// TODO Auto-generated method stub
SpringApplication.run(MainApplication.class, args);
}
}
效果展示:
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。