Spring 事务管理示例 JDBC

Spring Transaction Management是 Spring 框架中最广泛使用和最重要的功能之一。Transaction Management 是任何企业应用程序中的一个微不足道的任务。我们已经学会了如何使用 JDBC API for Transaction Management. Spring 提供了广泛的交易管理支持,并帮助开发人员更多地关注业务逻辑,而不是担心任何系统故障的数据收集完整性。

春季交易管理

spring transaction management, spring @Transactional, Spring JDBCTemplate, spring transaction Some of the benefits of using Spring Transaction Management are:

在这个模型中,Spring 使用 AOP 来提供数据完整性,这是最受欢迎的方法,在大多数情况下 2 支持 JDBC、Hibernate、JPA、JDO、JTA 等大多数交易 API。我们所需要做的就是使用适当的交易管理执行类别,例如:org.springframework.jdbc.datasource.DriverManagerDataSource 用于 JDBC 交易管理和 `org.springframework.ormhibernate3.HibernateTransactionManager’ 如果我们使用 Hibernate 作为 ORM 工具(_MK1BR_3)3 支持使用「TransactionTemplate」或「PlatformTransactionManager implementation」的程序式交易管理。

我们希望在交易管理器中使用的大多数功能都是通过声明交易管理来支持的,因此我们会为我们的示例项目使用这种方法。

春季交易管理 JDBC 示例

我们将创建一个简单的春季JDBC项目,我们将在单一交易中更新多个表。交易只应在所有JDBC陈述成功执行时进行承诺,否则它应该回归以避免数据不一致。如果你知道JDBC交易管理,你可能会认为我们可以通过将自动承诺设置为连接的虚假,并根据所有陈述的结果进行承诺。显然,我们可以这样做,但这将导致大量的锅炉板代码仅用于交易管理。同样的代码也会出现在我们正在寻找交易管理的所有地方,导致紧密连接和不可维护的代码。春季交易管理解决了这些问题,通过使用面向导向的程序在我们的应用程序中实现松散的连接和避免锅炉板代码。让我们看看春天如何用一个简单的例子跳跃。在我们的春季项目

春季交易管理 - 数据库设置

我们将为我们的使用创建两个表,并在一个交易中更新它们。

1CREATE TABLE `Customer` (
2  `id` int(11) unsigned NOT NULL,
3  `name` varchar(20) DEFAULT NULL,
4  PRIMARY KEY (`id`)
5) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1CREATE TABLE `Address` (
2  `id` int(11) unsigned NOT NULL,
3  `address` varchar(20) DEFAULT NULL,
4  `country` varchar(20) DEFAULT NULL,
5  PRIMARY KEY (`id`)
6) ENGINE=InnoDB DEFAULT CHARSET=utf8;

We could define foreign-key relationship here from Address id column to Customer id column, but for simplicity I am not having any constraint defined here. Our Database setup is ready for spring transaction management project, lets create a simple Spring Maven Project in the Spring Tool Suite. Our final project structure will look like below image. Spring Transaction Management Example Let's look into each of the pieces one by one, together they will provide a simple spring transaction management example with JDBC.

春季交易管理 - Maven 依赖

由于我们正在使用 JDBC API,我们将不得不在我们的应用程序中包含 spring-jdbc 依赖性,我们还需要 MySQL 数据库驱动程序连接到 mysql 数据库,所以我们也将包含 mysql-connector-java 依赖性。 spring-tx 文物提供了交易管理依赖性,通常由 STS 自动包含,但如果不是,那么您也需要包括它。

 1<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
 2    xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 3    <modelVersion>4.0.0</modelVersion>
 4    <groupId>org.springframework.samples</groupId>
 5    <artifactId>SpringJDBCTransactionManagement</artifactId>
 6    <version>0.0.1-SNAPSHOT</version>
 7
 8    <properties>
 9
10    	<!-- Generic properties -->
11    	<java.version>1.7</java.version>
12    	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
13    	<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
14
15    	<!-- Spring -->
16    	<spring-framework.version>4.0.2.RELEASE</spring-framework.version>
17
18    	<!-- Logging -->
19    	<logback.version>1.0.13</logback.version>
20    	<slf4j.version>1.7.5</slf4j.version>
21
22    	<!-- Test -->
23    	<junit.version>4.11</junit.version>
24
25    </properties>
26
27    <dependencies>
28    	<!-- Spring and Transactions -->
29    	<dependency>
30    		<groupId>org.springframework</groupId>
31    		<artifactId>spring-context</artifactId>
32    		<version>${spring-framework.version}</version>
33    	</dependency>
34    	<dependency>
35    		<groupId>org.springframework</groupId>
36    		<artifactId>spring-tx</artifactId>
37    		<version>${spring-framework.version}</version>
38    	</dependency>
39
40    	<!-- Spring JDBC and MySQL Driver -->
41    	<dependency>
42    		<groupId>org.springframework</groupId>
43    		<artifactId>spring-jdbc</artifactId>
44    		<version>${spring-framework.version}</version>
45    	</dependency>
46    	<dependency>
47    		<groupId>mysql</groupId>
48    		<artifactId>mysql-connector-java</artifactId>
49    		<version>5.0.5</version>
50    	</dependency>
51
52    	<!-- Logging with SLF4J & LogBack -->
53    	<dependency>
54    		<groupId>org.slf4j</groupId>
55    		<artifactId>slf4j-api</artifactId>
56    		<version>${slf4j.version}</version>
57    		<scope>compile</scope>
58    	</dependency>
59    	<dependency>
60    		<groupId>ch.qos.logback</groupId>
61    		<artifactId>logback-classic</artifactId>
62    		<version>${logback.version}</version>
63    		<scope>runtime</scope>
64    	</dependency>
65
66    	<!-- Test Artifacts -->
67    	<dependency>
68    		<groupId>org.springframework</groupId>
69    		<artifactId>spring-test</artifactId>
70    		<version>${spring-framework.version}</version>
71    		<scope>test</scope>
72    	</dependency>
73    	<dependency>
74    		<groupId>junit</groupId>
75    		<artifactId>junit</artifactId>
76    		<version>${junit.version}</version>
77    		<scope>test</scope>
78    	</dependency>
79
80    </dependencies>
81</project>

我已经更新了春季版本到今天的最新版本. 确保MySQL数据库驱动程序与您的mysql安装兼容。

春季交易管理 - 模型类

我们将创建两个Java Beans,客户端和地址,将其绘制到我们的表中。

 1package com.journaldev.spring.jdbc.model;
 2
 3public class Address {
 4
 5    private int id;
 6    private String address;
 7    private String country;
 8    
 9    public int getId() {
10    	return id;
11    }
12    public void setId(int id) {
13    	this.id = id;
14    }
15    public String getAddress() {
16    	return address;
17    }
18    public void setAddress(String address) {
19    	this.address = address;
20    }
21    public String getCountry() {
22    	return country;
23    }
24    public void setCountry(String country) {
25    	this.country = country;
26    }
27    
28}
 1package com.journaldev.spring.jdbc.model;
 2
 3public class Customer {
 4
 5    private int id;
 6    private String name;
 7    private Address address;
 8    
 9    public int getId() {
10    	return id;
11    }
12    public void setId(int id) {
13    	this.id = id;
14    }
15    public String getName() {
16    	return name;
17    }
18    public void setName(String name) {
19    	this.name = name;
20    }
21    public Address getAddress() {
22    	return address;
23    }
24    public void setAddress(Address address) {
25    	this.address = address;
26    }
27    
28}

请注意,当我们为客户实现DAO时,我们将获得客户和地址表的数据,我们将为这些表执行两个单独的插入查询,这就是为什么我们需要交易管理以避免数据不一致。

春季交易管理 - DAO 实施

讓我們實施「顧客豆」的DAO,為了簡單,我們只有一種方法可以將記錄插入到顧客和地址表中。

1package com.journaldev.spring.jdbc.dao;
2
3import com.journaldev.spring.jdbc.model.Customer;
4
5public interface CustomerDAO {
6
7    public void create(Customer customer);
8}
 1package com.journaldev.spring.jdbc.dao;
 2
 3import javax.sql.DataSource;
 4
 5import org.springframework.jdbc.core.JdbcTemplate;
 6
 7import com.journaldev.spring.jdbc.model.Customer;
 8
 9public class CustomerDAOImpl implements CustomerDAO {
10
11    private DataSource dataSource;
12
13    public void setDataSource(DataSource dataSource) {
14    	this.dataSource = dataSource;
15    }
16
17    @Override
18    public void create(Customer customer) {
19    	String queryCustomer = "insert into Customer (id, name) values (?,?)";
20    	String queryAddress = "insert into Address (id, address,country) values (?,?,?)";
21
22    	JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
23
24    	jdbcTemplate.update(queryCustomer, new Object[] { customer.getId(),
25    			customer.getName() });
26    	System.out.println("Inserted into Customer Table Successfully");
27    	jdbcTemplate.update(queryAddress, new Object[] { customer.getId(),
28    			customer.getAddress().getAddress(),
29    			customer.getAddress().getCountry() });
30    	System.out.println("Inserted into Address Table Successfully");
31    }
32
33}

请注意,CustomerDAO 实现并不关心交易管理,因此我们正在实现关切的分离,因为有时我们从第三方获得 DAO 实现,我们对这些类别没有控制权。

春季交易宣言管理 - 服务

让我们创建一个客户服务,该服务将使用 CustomerDAO 实现,并在单一方法中在客户和地址表中插入记录时提供交易管理。

1package com.journaldev.spring.jdbc.service;
2
3import com.journaldev.spring.jdbc.model.Customer;
4
5public interface CustomerManager {
6
7    public void createCustomer(Customer cust);
8}
 1package com.journaldev.spring.jdbc.service;
 2
 3import org.springframework.transaction.annotation.Transactional;
 4
 5import com.journaldev.spring.jdbc.dao.CustomerDAO;
 6import com.journaldev.spring.jdbc.model.Customer;
 7
 8public class CustomerManagerImpl implements CustomerManager {
 9
10    private CustomerDAO customerDAO;
11
12    public void setCustomerDAO(CustomerDAO customerDAO) {
13    	this.customerDAO = customerDAO;
14    }
15
16    @Override
17    @Transactional
18    public void createCustomer(Customer cust) {
19    	customerDAO.create(cust);
20    }
21
22}

如果你注意到 CustomerManager 实现,它只是使用 CustomerDAO 实现来创建客户,但通过注释创建Customer() 方法来提供声明性交易管理,使用 @Transactional 注释. 这是我们在我们的代码中所需要做的,以获得春季交易管理的好处。 @Transactional 注释可以应用于方法以及整个类别。 如果你想要你的所有方法都有交易管理功能,你应该用这个注释注释你的类。 阅读更多关于注释在 Java 注释教程

春季交易管理 - Bean 配置

创建一个名为spring.xml的春豆配置文件,我们将在我们的测试程序中使用它来生成春豆,并执行我们的JDBC程序来测试交易管理。

 1<?xml version="1.0" encoding="UTF-8"?>
 2<beans xmlns="https://www.springframework.org/schema/beans"
 3    xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:context="https://www.springframework.org/schema/context"
 4    xmlns:tx="https://www.springframework.org/schema/tx"
 5    xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
 6    	https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-4.0.xsd
 7    	https://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
 8
 9    <!-- Enable Annotation based Declarative Transaction Management -->
10    <tx:annotation-driven proxy-target-class="true"
11    	transaction-manager="transactionManager" />
12
13    <!-- Creating TransactionManager Bean, since JDBC we are creating of type 
14    	DataSourceTransactionManager -->
15    <bean id="transactionManager"
16    	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
17    	<property name="dataSource" ref="dataSource" />
18    </bean>
19    
20    <!-- MySQL DB DataSource -->
21    <bean id="dataSource"
22    	class="org.springframework.jdbc.datasource.DriverManagerDataSource">
23
24    	<property name="driverClassName" value="com.mysql.jdbc.Driver" />
25    	<property name="url" value="jdbc:mysql://localhost:3306/TestDB" />
26    	<property name="username" value="pankaj" />
27    	<property name="password" value="pankaj123" />
28    </bean>
29
30    <bean id="customerDAO" class="com.journaldev.spring.jdbc.dao.CustomerDAOImpl">
31    	<property name="dataSource" ref="dataSource"></property>
32    </bean>
33
34    <bean id="customerManager" class="com.journaldev.spring.jdbc.service.CustomerManagerImpl">
35    	<property name="customerDAO" ref="customerDAO"></property>
36    </bean>
37
38</beans>

春豆配置文件中要注意的重要点是:

  • tx:注解驱动元素用于告诉斯普林特语境,我们正在使用以注解为基础的交易管理配置. ** 交易管理器属性用于提供交易管理器豆名。 交易管理器默认值为 _ 交易 经理,但我还是有它 以避免混淆。 ** 代理目标类 属性用于告诉Spring上下文使用基于类的代理,没有它,您将获得运行时的例外信息,例如线条"main" org.springframework.beans.factory.BeannotOf RequiredTypeException: Bean named 'CusterManager' 必须是类型 [com.journaldev.spring.jdbc.service.CustomerManagerImpl],但实际上是类型 [com.sun.proxy.$Proxy6]( ) * 由于我们使用JDBC,我们创建了"org.springframework.jdbc.datasource.Data SourceTransactionManager"类型的交易Manager豆. 这是非常重要的,我们应该根据我们的交易API的使用来使用适当的交易管理器执行类. dataSource_豆瓣用于创建DataSource对象,我们被要求提供驱动程序ClassName,url,用户名和密码等数据库配置属性. 根据本地设置更改这些值 。 () ( ) 我们正在将数据源输入到客户DAO-豆子中。 同样,我们向客户注射了_客户DAO_豆类 管理器_豆类定义. () (英语)

我们的设置已经准备好了,让我们创建一个简单的测试类来测试我们的交易管理实现。

 1package com.journaldev.spring.jdbc.main;
 2
 3import org.springframework.context.support.ClassPathXmlApplicationContext;
 4
 5import com.journaldev.spring.jdbc.model.Address;
 6import com.journaldev.spring.jdbc.model.Customer;
 7import com.journaldev.spring.jdbc.service.CustomerManager;
 8import com.journaldev.spring.jdbc.service.CustomerManagerImpl;
 9
10public class TransactionManagerMain {
11
12    public static void main(String[] args) {
13    	ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
14    			"spring.xml");
15
16    	CustomerManager customerManager = ctx.getBean("customerManager",
17    			CustomerManagerImpl.class);
18
19    	Customer cust = createDummyCustomer();
20    	customerManager.createCustomer(cust);
21
22    	ctx.close();
23    }
24
25    private static Customer createDummyCustomer() {
26    	Customer customer = new Customer();
27    	customer.setId(2);
28    	customer.setName("Pankaj");
29    	Address address = new Address();
30    	address.setId(2);
31    	address.setCountry("India");
32    	// setting value more than 20 chars, so that SQLException occurs
33    	address.setAddress("Albany Dr, San Jose, CA 95129");
34    	customer.setAddress(address);
35    	return customer;
36    }
37
38}

请注意,我正在明确设置地址列值太长时间,以便在将数据插入地址表时获得例外。

 1Mar 29, 2014 7:59:32 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
 2INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3fa99295: startup date [Sat Mar 29 19:59:32 PDT 2014]; root of context hierarchy
 3Mar 29, 2014 7:59:32 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
 4INFO: Loading XML bean definitions from class path resource [spring.xml]
 5Mar 29, 2014 7:59:32 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
 6INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
 7Inserted into Customer Table Successfully
 8Mar 29, 2014 7:59:32 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
 9INFO: Loading XML bean definitions from class path resource [org/springframework/jdbc/support/sql-error-codes.xml]
10Mar 29, 2014 7:59:32 PM org.springframework.jdbc.support.SQLErrorCodesFactory <init>
11INFO: SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
12Exception in thread "main" org.springframework.dao.DataIntegrityViolationException: PreparedStatementCallback; SQL [insert into Address (id, address,country) values (?,?,?)]; Data truncation: Data too long for column 'address' at row 1; nested exception is com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'address' at row 1
13    at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:100)
14    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
15    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
16    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
17    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658)
18    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:907)
19    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:968)
20    at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:978)
21    at com.journaldev.spring.jdbc.dao.CustomerDAOImpl.create(CustomerDAOImpl.java:27)
22    at com.journaldev.spring.jdbc.service.CustomerManagerImpl.createCustomer(CustomerManagerImpl.java:19)
23    at com.journaldev.spring.jdbc.service.CustomerManagerImpl$$FastClassBySpringCGLIB$$84f71441.invoke(<generated>)
24    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
25    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:711)
26    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
27    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
28    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
29    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
30    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
31    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
32    at com.journaldev.spring.jdbc.service.CustomerManagerImpl$$EnhancerBySpringCGLIB$$891ec7ac.createCustomer(<generated>)
33    at com.journaldev.spring.jdbc.main.TransactionManagerMain.main(TransactionManagerMain.java:20)
34Caused by: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'address' at row 1
35    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2939)
36    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1623)
37    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1715)
38    at com.mysql.jdbc.Connection.execSQL(Connection.java:3249)
39    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1268)
40    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1541)
41    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1455)
42    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1440)
43    at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:914)
44    at org.springframework.jdbc.core.JdbcTemplate$2.doInPreparedStatement(JdbcTemplate.java:907)
45    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:642)
46    ... 16 more

注意日志消息说,插入客户表的数据是成功的,但是MySQL数据库驱动程序抛出例外,这明确表明,对于地址列来说,数值太长了. 现在,如果你检查客户表, 你不会发现任何行 在那里意味着交易 完全回滚。 如果您想知道交易管理魔法在哪里发生, 请仔细查看日志, 注意Spring 框架创建的 AOP 和 代理 类 。 Spring 框架正在使用 Around 咨询为客户管理器创建代理类, 并且只有在方法成功返回时才进行交易 。 如果有任何例外,它只是把整个交易都退了. 我建议你读取春季AOP示例. "Spring AOP 示例教程 – Spect, Asspect, Point, Point, JointPoint, 说明, XML 配置"),以更多地了解Aspect Oriented编程模式. 全部用于Spring Contraction Management 示例,从下方链接下载样本项目,并与之玩弄以学习更多.

下载春季JDBC交易管理项目

Published At
Categories with 技术
Tagged with
comments powered by Disqus