风一更 -- 事务管理 -- Spring 事务
目标:精通- 事务是构建可靠企业级应用程序的最关键部分之一。
事务管理是横切关注点,不应该在业务逻辑中编码。
最合适的方式是允许以声明方式定义业务需求,让诸如 Spring, JEE 或 AOP之类的框架代替我们织入事务处理逻辑。
Spring 支持声明式和
编程式事务管理(Transaction API JTA)。
Spring 声明式事务,声明必须参与事务的相关方法(类或层中) 以及事务配置的细节.
1. spring 事务抽象层
2.JTA 实现全局事务: 需要跨越多个后端资源的全局事务。JTA Java Transaction API.
本地事务特定于单个事务资源,如 JDBC连接。
全局事务由容器管理且可以跨越多个资源。
Java 中,全局事务是通过JTA 实施的。与JTA兼容的事务管理器 通过各自的资源管理器连接到多个事务资源,而这些资源管理器
能够通过 XA 协议(一种定义了分布式事务的开放标准)与事务管理器进行通信。
并使用 2Phase commit ( 2PC ) 机制来确保所有后端数据源被更新 或 完全回滚。
如果任意后端资源失效,整个事务将回滚,因此对其他资源的更新也将回滚。
分布式事务 四部分。
1. 后端资源: RDBMS , MQ, ERP 系统 ;
2. 资源管理器,负责与后端资源交互,连接到 MySQL 数据库时,需要与 MySQL的 Java 连接器
提供的 MysqlXADataSource类进行交互。其他后端资源也都提供了自己的资源管理器.
3. JTA 事务管理器,负责管理,协调和同步参与事务的所有资源管理器的事务状态。我们使用XA协议,这是一种被广泛用于分布式事务处理的
开放标准。 JTA 事务管理器还支持 2PC, 以便所有更改一并提交,并且当任何资源更新失败时,回滚整个事务,从而导致任何资源都没有被更新。
整个机制由 Java Transaction Service (JTS) 规范指定 .
4. 应用程序,可以是应用程序本身,可以是运行应用程序的底层容器 或 Spring 框架,用来管理事务(开始,提交,回滚事务等)同时,应用程序
通过由JEE定义的各种标准与底层后端资源进行交互. APP 通过JDBC连接到 RDBMS, JMS连接到 MQ , Java EE Conector Architecture (JCA) 连接ERP.
兼容 JEE 的 app server JBOSS, GlassFish 都支持JTA, 通过JNDI查找提供事务,
对于独立应用程序或 Web容器 tomcat Jetty - Atomikos / Bitronix 提供JTA/XA 支持。
PlatformTransactionManager 接口使用 TransactionDefinition 和 TransactionStatus 接口来创建和管理事务。
实现:
CciLocalTransactionManager 类支持 JEE,JCA 和 通用客户端接口 CCI。
DataSourceTransactionManager 类用于通用JDBC 连接。
ORM 很多实现,JPA 类 JpaTransactionManager 类 和 Hibernate 5( HibernateTransactionManger)
JMS JMSTransactionManager 类支持 JMS2.0
JTA 通用实现 JtaTransactionManager
ACID 原子性,一致性,隔离性,持久性。如果维护这些事务属性取决于事务资源。
无法控制 ACD, 可以控制事务的传播和超时,以及配置事务是否应为只读并指定隔离级别。
Spring将这些设置封装在 TransactionDefinition 接口中.
该接口被用于Spring的事务支持的核心接口,即 PlatformTransactionManager接口,该接口的实现在特定平台(如JDBC 或 JTA)上
执行事务管理。
核心方法 PlatformTransactionManager.getTransaction() 将 TransactionDefinition 接口作为参数,并返回一个TransactionStatus接口。
该接口用于事务执行。设置事务事务结果,检查事务是否完成,或是否是新事务。
TransactionDefinition 接口




使用 JPA 和 Hibernate 作为实现数据访问逻辑的持久化层,
使用Spring Data JPA 及其 存储库 抽象简化基本数据库操作的开发。
使用注解是在Spring中定义事务需求的最常见方法。
优点是:事务需求可以与详细事务属性(超时,隔离级别,传播行为)一起在代码本身内定义,从而使应用程序更容易跟踪和维护。
XML <tx:annotation-drivern> 标记。
@EnableJpaRepository 注解。 启用 Spring Data JPA 的存储库抽象。
专业环境中,将 持久化配置 DAO 与 事务配置(服务)分开。


@Transaction 不带任何属性的,意味着
事务传播是必需的,隔离是底层资源的默认隔离级别(read commit ) ,超时是默认的, 模式是可读写。
方法级别的属性会覆盖 类级别属性,其他保持不变。
启用适当的日志,设置

在日志消息中看到与事务处理相关的信息。
在运行方法前,Spring的 JpaTransactionManager 使用默认属性创建了一个新的事务(事务名称与方法的完全限定类名相同),
但事务被设置为只读,然后提交查询,在完成且没有任何错误的情况下,事务被提交。
JpaTM负责处理事务的创建和提交操作.

@Transactional( readOnly = true ) 属性应该被应用于所有查找方法。
主要原因是,大多数持久化程序会对只读事务执行一定程度的优化,Hibernate 不会维护从数据库检索且只读的托管实例的快照.
Save() 方法,完成更新操作后,Spring 的 JpaTransactionManager触发事务提交,从而导致Hibernate刷新持久上下文并将底层JDBC连接提交给数据库。
Spring data 中, 由CrudRepository 接口定义的方法已经用适当的事务属性进行了标记.

控制传播


声明式事务管理的另一种常见方法是使用 Spring 的 AOP 支持,
需要封装不属于自己项目的事务代码,且无法对代码进行编辑以添加 @Transaction 注解.
<context:component-sacen> 用于声明式事务管理的包名,
最重要的标记是 <aop:config> 和 <tx:advice>
<aop:config> 为服务层中的所有操作(xx.service 包中的所有实现类)定义一个切入点。
该通知引用了 ID 为 txAdvice 的bean (由 <tx:advice> 标记定义)。
事务是通过 aop显示完成的, 不需要@Transactional 注解。
编程式事务
1. 将一个 PlatformTransactionManager 实例注入到bean中,并直接与事务管理器进行交互.
2.使用Spring 提供的 TransactionTemplate 类。




全局事务,分布式事务
企业级Java应用程序需要访问多个后端资源.
从外部业务合作伙伴受到的一条信息可能需要更新多个系统(CRM, ERP ) 的数据库,
生成一条消息,通过JMS 发送给 MQ,以便公司内部对该信息感兴趣的所有其他应用程序使用。
跨越多个后端资源的事务被称为 全局事务 / 分布式事务
全局事务的一个主要特征是保证了原子性,意味着所涉及的资源要么全部更新,要么都不更新。
还包括应该由 事务管理器处理的 复杂协调 和同步逻辑, JTA Java Transaction API 是实现全局事务的事实标准.
和本地事务一样, Spring 支持 JTA 事务,并从业务代码中隐藏相关逻辑。
在独立应用程序 或 Web 容器环境中使用 JTA 实现全局事务。 Atomikos, 一个被广泛使用的开源 JTA 事务管理器,
用于 非 JEE环境.
使用两个 JPA 实体管理器, 可以使用多个JPA持久化单元来区分后端数据库。
推荐使用 JtaTransactionManager.

emfBase bean中的属性 hibernate.transaction.factory_class和 hibernate.transaction.jtc.platform 这两个属性非常重要,
Hibernate 使用它们来查找底层的UserTransaction 和 TransactionManager bean, 从而参与正在管理全局事务的持久化上下文,
hibernate.transaction.coordinator_class, 让 Hibernate4 的 Atomikos 类使用 Hibernate5 所必须的。




配置Mysql 连接属性时,注意 serverTimezone UTC


Spring boot Atomikos 分布式事务
MySql script
ddl
1 create user 'prospring5_a'@'localhost' identified by 'prospring5_a'; 2 create schema MUSICDB_A; 3 GRANT ALL PRIVILEGES ON MUSICDB_A . * TO 'prospring5_a'@'localhost'; 4 flush privileges 5 6 create user 'prospring5_b'@'localhost' identified by 'prospring5_b'; 7 create schema MUSICDB_B; 8 GRANT ALL PRIVILEGES ON MUSICDB_B . * TO 'prospring5_b'@'localhost'; 9 flush privileges
dml
1 CREATE TABLE `musicdb_a`.`SINGER` ( 2 ID INT NOT NULL AUTO_INCREMENT, 3 FIRST_NAME VARCHAR(60) NOT NULL, 4 LAST_NAME VARCHAR(40) NOT NULL, 5 BIRTH_DATE DATE, 6 VERSION INT NOT NULL DEFAULT 0, 7 UNIQUE UQ_SINGER_1 (FIRST_NAME,LAST_NAME), 8 PRIMARY KEY(ID) 9 ); 10 11 CREATE TABLE `musicdb_a`.`ALBUM` ( 12 ID INT NOT NULL AUTO_INCREMENT, 13 SINGER_ID INT NOT NULL, 14 TITLE VARCHAR(100) NOT NULL, 15 RELEASE_DATE DATE, 16 VERSION INT NOT NULL DEFAULT 0, 17 UNIQUE UQ_ALBUM_1 (SINGER_ID,TITLE), 18 CONSTRAINT FK_ALBUM_SNGER_1 foreign key (SINGER_ID) 19 REFERENCES SINGER(ID), 20 PRIMARY KEY(ID) 21 ); 22 23 CREATE TABLE `musicdb_a`.`INSTRUMENT` ( 24 INSTRUMENT_ID VARCHAR(20) NOT NULL, 25 PRIMARY KEY(INSTRUMENT_ID) 26 ); 27 28 CREATE TABLE `musicdb_a`.`SINGER_INSTRUMENT` ( 29 SINGER_ID INT NOT NULL, 30 INSTRUMENT_ID VARCHAR(20) NOT NULL, 31 PRIMARY KEY(SINGER_ID,INSTRUMENT_ID), 32 CONSTRAINT FK_SINGER_INSTRUMENT_1 foreign key(SINGER_ID) 33 REFERENCES SINGER(ID) ON DELETE CASCADE, 34 CONSTRAINT FK_SINGER_INSTRUMENT_2 foreign key(INSTRUMENT_ID) 35 REFERENCES INSTRUMENT(INSTRUMENT_ID) 36 ); 37 38 CREATE TABLE `musicdb_b`.`SINGER` ( 39 ID INT NOT NULL AUTO_INCREMENT, 40 FIRST_NAME VARCHAR(60) NOT NULL, 41 LAST_NAME VARCHAR(40) NOT NULL, 42 BIRTH_DATE DATE, 43 VERSION INT NOT NULL DEFAULT 0, 44 UNIQUE UQ_SINGER_1 (FIRST_NAME,LAST_NAME), 45 PRIMARY KEY(ID) 46 ); 47 48 CREATE TABLE `musicdb_b`.`ALBUM` ( 49 ID INT NOT NULL AUTO_INCREMENT, 50 SINGER_ID INT NOT NULL, 51 TITLE VARCHAR(100) NOT NULL, 52 RELEASE_DATE DATE, 53 VERSION INT NOT NULL DEFAULT 0, 54 UNIQUE UQ_ALBUM_1 (SINGER_ID,TITLE), 55 CONSTRAINT FK_ALBUM_SNGER_1 foreign key (SINGER_ID) 56 REFERENCES SINGER(ID), 57 PRIMARY KEY(ID) 58 ); 59 60 CREATE TABLE `musicdb_b`.`INSTRUMENT` ( 61 INSTRUMENT_ID VARCHAR(20) NOT NULL, 62 PRIMARY KEY(INSTRUMENT_ID) 63 ); 64 65 CREATE TABLE `musicdb_b`.`SINGER_INSTRUMENT` ( 66 SINGER_ID INT NOT NULL, 67 INSTRUMENT_ID VARCHAR(20) NOT NULL, 68 PRIMARY KEY(SINGER_ID,INSTRUMENT_ID), 69 CONSTRAINT FK_SINGER_INSTRUMENT_1 foreign key(SINGER_ID) 70 REFERENCES SINGER(ID) ON DELETE CASCADE, 71 CONSTRAINT FK_SINGER_INSTRUMENT_2 foreign key(INSTRUMENT_ID) 72 REFERENCES INSTRUMENT(INSTRUMENT_ID) 73 );
pom.xml
1 <dependencies> 2 <dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-jdbc</artifactId> 5 </dependency> 6 <dependency> 7 <groupId>org.springframework.boot</groupId> 8 <artifactId>spring-boot-starter-data-jdbc</artifactId> 9 </dependency> 10 <dependency> 11 <groupId>org.springframework.boot</groupId> 12 <artifactId>spring-boot-starter-data-jpa</artifactId> 13 </dependency> 14 <dependency> 15 <groupId>org.apache.commons</groupId> 16 <artifactId>commons-lang3</artifactId> 17 <version>3.1</version> 18 </dependency> 19 <dependency> 20 <groupId>com.google.guava</groupId> 21 <artifactId>guava</artifactId> 22 <version>30.1-jre</version> 23 </dependency> 24 <dependency> 25 <groupId>org.hibernate</groupId> 26 <artifactId>hibernate-core</artifactId> 27 <version>6.0.0.Alpha6</version> 28 <type>pom</type> 29 </dependency> 30 <dependency> 31 <groupId>org.hibernate</groupId> 32 <artifactId>hibernate-entitymanager</artifactId> 33 <version>6.0.0.Alpha6</version> 34 <type>pom</type> 35 </dependency> 36 <dependency> 37 <groupId>org.hibernate.orm</groupId> 38 <artifactId>hibernate-ehcache</artifactId> 39 <version>6.0.0.Alpha6</version> 40 </dependency> 41 42 <dependency> 43 <groupId>org.springframework</groupId> 44 <artifactId>spring-tx</artifactId> 45 <version>5.3.4</version> 46 </dependency> 47 48 <dependency> 49 <groupId>org.springframework</groupId> 50 <artifactId>spring-aspects</artifactId> 51 <version>5.3.4</version> 52 </dependency> 53 54 <dependency> 55 <groupId>com.atomikos</groupId> 56 <artifactId>transactions-hibernate4</artifactId> 57 <version>5.0.8</version> 58 </dependency> 59 60 <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support --> 61 <dependency> 62 <groupId>org.springframework</groupId> 63 <artifactId>spring-context-support</artifactId> 64 <version>5.3.4</version> 65 </dependency> 66 <dependency> 67 <groupId>org.springframework</groupId> 68 <artifactId>spring-orm</artifactId> 69 <version>5.3.4</version> 70 </dependency> 71 72 <dependency> 73 <groupId>mysql</groupId> 74 <artifactId>mysql-connector-java</artifactId> 75 <scope>runtime</scope> 76 </dependency> 77 78 <dependency> 79 <groupId>org.springframework.boot</groupId> 80 <artifactId>spring-boot-starter-test</artifactId> 81 <scope>test</scope> 82 <exclusions> 83 <exclusion> 84 <groupId>org.junit.vintage</groupId> 85 <artifactId>junit-vintage-engine</artifactId> 86 </exclusion> 87 </exclusions> 88 </dependency> 89 </dependencies>


XAJpaConfig
1 package org.techroad.repositorydis.demo.config; 2 3 import com.atomikos.jdbc.AtomikosDataSourceBean; 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 import org.springframework.context.annotation.Bean; 7 import org.springframework.context.annotation.Configuration; 8 import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 9 import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 10 import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; 11 12 import javax.persistence.EntityManager; 13 import javax.persistence.EntityManagerFactory; 14 import javax.print.DocFlavor; 15 import javax.sql.DataSource; 16 import java.util.Properties; 17 18 /** 19 * responsibility : 20 * 21 * @author : cj 22 * @date : 2021/3/14 23 */ 24 25 @Configuration 26 @EnableJpaRepositories 27 public class XAJpaConfig { 28 29 private static Logger logger = LoggerFactory.getLogger(XAJpaConfig.class); 30 31 @SuppressWarnings("unchecked") 32 @Bean(initMethod = "init", destroyMethod = "close") 33 public DataSource dataSourceA() { 34 return getDataSourceWith("XADBMSA", xaAproperties()); 35 } 36 37 @Bean 38 public Properties xaAproperties() { 39 Properties xaAprop = new Properties(); 40 xaAprop.setProperty("databaseName", "musicdb_a"); 41 xaAprop.setProperty("user", "prospring5_a"); 42 xaAprop.setProperty("password", "prospring5_a"); 43 xaAprop.setProperty("serverTimezone", "UTC"); 44 return xaAprop; 45 } 46 47 @SuppressWarnings("unchecked") 48 @Bean(initMethod = "init", destroyMethod = "close") 49 public DataSource dataSourceB() { 50 return getDataSourceWith("XADBMSB", xaBproperties()); 51 } 52 53 private DataSource getDataSourceWith(String uqResouceName, Properties properties) { 54 try { 55 AtomikosDataSourceBean dataSource = new AtomikosDataSourceBean(); 56 dataSource.setUniqueResourceName(uqResouceName); 57 dataSource.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource"); 58 dataSource.setXaProperties(properties); 59 dataSource.setPoolSize(1); 60 return dataSource; 61 } catch (Exception e) { 62 logger.error("Populator DataSource bean ccannot be created!"); 63 return null; 64 } 65 } 66 67 @Bean 68 public Properties xaBproperties() { 69 Properties xaBprop = new Properties(); 70 xaBprop.setProperty("databaseName", "musicdb_b"); 71 xaBprop.setProperty("user", "prospring5_b"); 72 xaBprop.setProperty("password", "prospring5_b"); 73 xaBprop.setProperty("serverTimezone", "UTC"); 74 return xaBprop; 75 } 76 77 @Bean 78 public Properties hibernateProperties() { 79 Properties hibernateProp = new Properties(); 80 hibernateProp.put("hibernate.transaction.factory_class", "org.hibernate.transaction.JTATransactionFactory"); 81 hibernateProp.put("hibernate.transaction.jta.platform", "com.atomikos.icatch.jta.hibernate4.AtomikosPlatform"); 82 hibernateProp.put("hibernate.transaction.coordinator_class", "jta"); 83 hibernateProp.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect"); 84 hibernateProp.put("hibernate.show_sql", true); 85 hibernateProp.put("hibernate.max_fetch_depth", 3); 86 hibernateProp.put("hibernate.jdbc.batch_size", 10); 87 hibernateProp.put("hibernate.jdbc.fetch_size", 50); 88 return hibernateProp; 89 } 90 91 @Bean 92 public EntityManagerFactory emfA() { 93 return getEMF(dataSourceA(), "emfA"); 94 } 95 96 @Bean 97 public EntityManagerFactory emfB() { 98 return getEMF(dataSourceB(), "emfB"); 99 } 100 101 private EntityManagerFactory getEMF(DataSource dataSource, String unitName) { 102 LocalContainerEntityManagerFactoryBean factoryBean = 103 new LocalContainerEntityManagerFactoryBean(); 104 factoryBean.setPackagesToScan("org.techroad.repositorydis.demo"); 105 factoryBean.setDataSource(dataSource); 106 factoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); 107 factoryBean.setJpaProperties(hibernateProperties()); 108 factoryBean.setPersistenceUnitName(unitName); 109 factoryBean.afterPropertiesSet(); 110 return factoryBean.getObject(); 111 } 112 }
ServiceConfig
1 package org.techroad.repositorydis.demo.config; 2 3 import org.springframework.orm.jpa.JpaSystemException; 4 import org.springframework.stereotype.Repository; 5 import org.springframework.stereotype.Service; 6 import org.springframework.transaction.annotation.Transactional; 7 8 import javax.persistence.EntityManager; 9 import javax.persistence.PersistenceContext; 10 import javax.persistence.PersistenceException; 11 import java.util.List; 12 13 /** 14 * responsibility : 15 * 16 * @author : cj 17 * @date : 2021/3/14 18 */ 19 20 @Service("singerService") 21 @Repository 22 @Transactional 23 public class SingerServiceImpl implements SingerService { 24 25 @PersistenceContext(unitName = "emfA") 26 private EntityManager emfA; 27 28 @PersistenceContext(unitName = "emfB") 29 private EntityManager emfB; 30 31 @Override 32 public Singer save(Singer singer) { 33 Singer singerB = new Singer(); 34 singerB.setFirstName(singer.getFirstName()); 35 singerB.setLastName(singer.getLastName()); 36 if ((Object) singer.getId() == null) { 37 emfA.persist(singer); 38 emfB.persist(singerB); 39 // throw new JpaSystemException 40 // (new PersistenceException()); 41 } else { 42 emfA.merge(singer); 43 emfB.merge(singerB); 44 } 45 return singer; 46 } 47 48 private static final String FIND_ALL = "select s from Singer s"; 49 50 @Override 51 @Transactional(readOnly = true) 52 public List<Singer> findAll() { 53 List<Singer> singersFromA = findAllInA(); 54 List<Singer> singersFromB = findAllInB(); 55 if (singersFromA.size() != singersFromB.size()) { 56 //todo check ex 57 /* throw new AsyncXAResourcesException("" + 58 "XA resources do not contain the same exception data.");*/ 59 throw new JpaSystemException( 60 new PersistenceException("XA resources do not contain the same exception data.")); 61 } 62 63 Singer sA = singersFromA.get(0); 64 Singer sB = singersFromB.get(0); 65 if (!sA.getFirstName().equals(sB.getFirstName())) { 66 /* throw new AsyncXAResourcesException("" + 67 "XA resources do not contain the same exception data.")*/ 68 throw new JpaSystemException( 69 new PersistenceException("XA resources do not contain the same exception data.")); 70 } 71 return List.of(sA, sB); 72 } 73 74 private List<Singer> findAllInA() { 75 return emfA.createQuery(FIND_ALL).getResultList(); 76 } 77 78 private List<Singer> findAllInB() { 79 return emfB.createQuery(FIND_ALL).getResultList(); 80 } 81 }
SingerService
1 package org.techroad.repositorydis.demo.config; 2 3 import java.util.List; 4 5 /** 6 * responsibility : 7 * 8 * @author : cj 9 * @date : 2021/3/14 10 */ 11 12 public interface SingerService { 13 Singer save(Singer singer); 14 List<Singer> findAll(); 15 }
Singer
1 package org.techroad.repositorydis.demo.config; 2 3 import javax.persistence.*; 4 import java.io.Serializable; 5 import java.util.Date; 6 import java.util.HashSet; 7 import java.util.Set; 8 9 import static javax.persistence.GenerationType.IDENTITY; 10 11 /** 12 * responsibility : 13 * 14 * @author : cj 15 * @date : 2021/3/14 16 */ 17 18 @Entity 19 @Table(name = "singer") 20 public class Singer implements Serializable { 21 @Id 22 @GeneratedValue(strategy = IDENTITY) 23 @Column(name = "ID") 24 public long id; 25 26 @Version 27 @Column(name = "VERSION") 28 public int version; 29 30 @Temporal(TemporalType.DATE) 31 @Column(name = "BIRTH_DATE") 32 public Date birthDate; 33 34 @Column(name = "FIRST_NAME") 35 public String firstName; 36 37 @Column(name = "LAST_NAME") 38 public String lastName; 39 40 @OneToMany(mappedBy = "singer", cascade = CascadeType.ALL, orphanRemoval = true) 41 private Set<Album> albums = new HashSet<>(); 42 43 @ManyToMany 44 @JoinTable(name = "singer_instrument", 45 joinColumns = @JoinColumn(name = "SINGER_ID"), 46 inverseJoinColumns = @JoinColumn(name = "INSTRUMENT_ID")) 47 private Set<Instrument> instruments = new HashSet<>(); 48 49 public Set<Album> getAlbums() { 50 return albums; 51 } 52 53 public void setAlbums(Set<Album> albums) { 54 this.albums = albums; 55 } 56 57 public boolean addAlbum(Album album){ 58 album.setSinger(this); 59 return getAlbums().add(album); 60 } 61 62 public void deleteAlbum(Album album){ 63 getAlbums().remove(album); 64 } 65 66 public Set<Instrument> getInstruments() { 67 return instruments; 68 } 69 70 public void setInstruments(Set<Instrument> instruments) { 71 this.instruments = instruments; 72 } 73 74 public long getId() { 75 return id; 76 } 77 78 public int getVersion() { 79 return version; 80 } 81 82 public Date getBirthDate() { 83 return birthDate; 84 } 85 86 public String getFirstName() { 87 return firstName; 88 } 89 90 public String getLastName() { 91 return lastName; 92 } 93 94 public void setBirthDate(Date birthDate) { 95 this.birthDate = birthDate; 96 } 97 98 public void setFirstName(String firstName) { 99 this.firstName = firstName; 100 } 101 102 public void setLastName(String lastName) { 103 this.lastName = lastName; 104 } 105 106 @Override 107 public String toString() { 108 return "Singer{" + 109 "id=" + id + 110 ", version=" + version + 111 ", birthDate=" + birthDate + 112 ", firstName='" + firstName + '\'' + 113 ", lastName='" + lastName + '\'' + 114 '}'; 115 } 116 117 public void removeAlbum(Album album) { 118 getAlbums().remove(album); 119 } 120 }
Album
1 package org.techroad.repositorydis.demo.config; 2 3 import javax.persistence.*; 4 import java.io.Serializable; 5 import java.util.Date; 6 7 /** 8 * responsibility : 9 * 10 * @author : cj 11 * @date : 2021/3/14 12 */ 13 14 @Entity 15 @Table(name="album") 16 public class Album implements Serializable { 17 public Album(){} 18 19 @Id 20 @GeneratedValue(strategy= GenerationType.IDENTITY) 21 @Column(name="ID") 22 private long id; 23 24 @Version 25 @Column(name="version") 26 private int version; 27 28 @Column(name="title") 29 private String title; 30 31 @Temporal(TemporalType.DATE) 32 @Column(name="RELEASE_DATE") 33 private Date releaseDate; 34 35 @ManyToOne 36 @JoinColumn(name="SINGER_ID") 37 private Singer singer; 38 39 public Album(String title,Date releaseDate){ 40 this.title = title; 41 this.releaseDate = releaseDate; 42 } 43 44 public long getId() { 45 return id; 46 } 47 48 public void setId(long id) { 49 this.id = id; 50 } 51 52 public int getVersion() { 53 return version; 54 } 55 56 public void setVersion(int version) { 57 this.version = version; 58 } 59 60 public String getTitle() { 61 return title; 62 } 63 64 public void setTitle(String title) { 65 this.title = title; 66 } 67 68 public Date getReleaseDate() { 69 return releaseDate; 70 } 71 72 public void setReleaseDate(Date releaseDate) { 73 this.releaseDate = releaseDate; 74 } 75 76 public Singer getSinger() { 77 return singer; 78 } 79 80 public void setSinger(Singer singer) { 81 this.singer = singer; 82 } 83 84 @Override 85 public String toString() { 86 return "Album{" + 87 "id=" + id + 88 ", version=" + version + 89 ", title='" + title + '\'' + 90 ", releaseDate=" + releaseDate + 91 '}'; 92 } 93 }
Instrument
1 package org.techroad.repositorydis.demo.config; 2 3 import javax.persistence.*; 4 import java.io.Serializable; 5 import java.util.HashSet; 6 import java.util.Set; 7 8 /** 9 * responsibility : 10 * 11 * @author : cj 12 * @date : 2021/3/14 13 */ 14 15 @Entity 16 @Table(name="instrument") 17 public class Instrument implements Serializable { 18 @Id 19 @GeneratedValue(strategy = GenerationType.IDENTITY) 20 @Column(name="INSTRUMENT_ID") 21 private String instrumentId; 22 23 @ManyToMany 24 @JoinTable(name="singer_instrument", 25 joinColumns = @JoinColumn(name="INSTRUMENT_ID"), 26 inverseJoinColumns = @JoinColumn(name="SINGER_ID")) 27 private Set<Singer> singers = new HashSet<>(); 28 29 public String getInstrumentId() { 30 return instrumentId; 31 } 32 33 public Set<Singer> getSingers() { 34 return singers; 35 } 36 37 public void setInstrumentId(String instrumentId) { 38 this.instrumentId = instrumentId; 39 } 40 41 public void setSingers(Set<Singer> singers) { 42 this.singers = singers; 43 } 44 45 @Override 46 public String toString() { 47 return "Instrument{" + 48 "instrumentId='" + instrumentId + '\'' + 49 '}'; 50 } 51 }
DempApplication
1 package org.techroad.repositorydis.demo; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 import org.springframework.beans.factory.annotation.Autowired; 6 import org.springframework.boot.ApplicationArguments; 7 import org.springframework.boot.ApplicationRunner; 8 import org.springframework.boot.SpringApplication; 9 import org.springframework.boot.autoconfigure.SpringBootApplication; 10 import org.springframework.stereotype.Component; 11 import org.techroad.repositorydis.demo.config.Singer; 12 import org.techroad.repositorydis.demo.config.SingerService; 13 14 import java.util.Date; 15 import java.util.GregorianCalendar; 16 import java.util.List; 17 18 @SpringBootApplication 19 public class DemoApplication { 20 21 private static Logger logger = LoggerFactory.getLogger(DemoApplication.class); 22 23 public static void main(String[] args) { 24 SpringApplication.run(DemoApplication.class, args); 25 } 26 27 @Component 28 class TxJtaDemo implements ApplicationRunner{ 29 30 @Autowired 31 SingerService singerService; 32 33 @Override 34 public void run(ApplicationArguments args) throws Exception { 35 Singer singer = new Singer(); 36 singer.setFirstName("Lola"); 37 singer.setLastName("prince"); 38 singer.setBirthDate(new Date(new GregorianCalendar(1990,2,5) 39 .getTime().getTime())); 40 singerService.save(singer); 41 if((Object)singer.getId() != null){ 42 logger.info("-------> Singer saved successfully"); 43 }else { 44 logger.error("-------> Singer was not saved, check the config!"); 45 } 46 47 List<Singer> bothSideSingers = singerService.findAll(); 48 if(bothSideSingers.size() != 6){ 49 logger.error("-------> something went wrong."); 50 }else { 51 logger.info("-------> singers from both DBs: " + bothSideSingers); 52 } 53 } 54 } 55 56 }

浙公网安备 33010602011771号