Skip to content

Commit b4a0ee2

Browse files
ZPZP1zhangpeng
and
zhangpeng
authored
feat:增加多数据源事务同步机制&多数据源事务单元测试 (#581)
* feat:增加事务同步机制 --------- Co-authored-by: zhangpeng <[email protected]>
1 parent d9d3b58 commit b4a0ee2

File tree

30 files changed

+1467
-5
lines changed

30 files changed

+1467
-5
lines changed

dynamic-datasource-spring-boot-common/src/main/java/com/baomidou/dynamic/datasource/spring/boot/autoconfigure/DynamicDataSourceAssistConfiguration.java

+11
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
2424
import com.baomidou.dynamic.datasource.provider.YmlDynamicDataSourceProvider;
2525
import com.baomidou.dynamic.datasource.strategy.DynamicDataSourceStrategy;
26+
import com.baomidou.dynamic.datasource.tx.DsTxEventListenerFactory;
2627
import lombok.RequiredArgsConstructor;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2729
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2830
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2931
import org.springframework.context.annotation.Bean;
@@ -73,4 +75,13 @@ public DefaultDataSourceCreator dataSourceCreator(List<DataSourceCreator> dataSo
7375
creator.setSeataMode(properties.getSeataMode());
7476
return creator;
7577
}
78+
79+
@Configuration
80+
static class DsTxEventListenerFactoryConfiguration {
81+
@Bean
82+
@ConditionalOnMissingBean
83+
public DsTxEventListenerFactory dsTxEventListenerFactory() {
84+
return new DsTxEventListenerFactory();
85+
}
86+
}
7687
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright © 2018 organization baomidou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.baomidou.dynamic.datasource.fixture.v1;
17+
18+
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
19+
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
20+
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
21+
import com.baomidou.dynamic.datasource.fixture.v1.service.tx.*;
22+
import com.baomidou.dynamic.datasource.tx.TransactionContext;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.boot.SpringApplication;
26+
import org.springframework.boot.autoconfigure.SpringBootApplication;
27+
import org.springframework.boot.test.context.SpringBootTest;
28+
import org.springframework.transaction.support.TransactionSynchronization;
29+
30+
import javax.sql.DataSource;
31+
import java.util.Arrays;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.junit.jupiter.api.Assertions.assertThrows;
35+
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
36+
37+
38+
@SpringBootTest(classes = DsTransactionalApplication.class, webEnvironment = RANDOM_PORT)
39+
public class DsTransactionalTest {
40+
@Autowired
41+
private OrderService orderService;
42+
43+
@Autowired
44+
private AccountService accountService;
45+
46+
@Autowired
47+
private ProductService productService;
48+
49+
@Autowired
50+
DataSource dataSource;
51+
52+
@Autowired
53+
DefaultDataSourceCreator dataSourceCreator;
54+
private DynamicRoutingDataSource ds;
55+
56+
@Test
57+
public void testDsTransactional() {
58+
DataSourceProperty orderDataSourceProperty = createDataSourceProperty("order");
59+
DataSourceProperty productDataSourceProperty = createDataSourceProperty("product");
60+
DataSourceProperty accountDataSourceProperty = createDataSourceProperty("account");
61+
ds = (DynamicRoutingDataSource) dataSource;
62+
ds.addDataSource(orderDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(orderDataSourceProperty));
63+
ds.addDataSource(productDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(productDataSourceProperty));
64+
ds.addDataSource(accountDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(accountDataSourceProperty));
65+
PlaceOrderRequest placeOrderRequest = new PlaceOrderRequest(1, 1, 22, OrderStatus.INIT);
66+
67+
//商品不足
68+
TransactionContext.registerSynchronization(new TransactionSynchronization() {
69+
@Override
70+
public void afterCompletion(int status) {
71+
if (status == STATUS_ROLLED_BACK) {
72+
placeOrderRequest.setOrderStatus(OrderStatus.FAIL);
73+
}
74+
}
75+
});
76+
assertThrows(RuntimeException.class, () -> orderService.placeOrder(placeOrderRequest));
77+
assertThat(placeOrderRequest.getOrderStatus()).isEqualTo(OrderStatus.FAIL);
78+
assertThat(orderService.selectOrders()).isEmpty();
79+
assertThat(accountService.selectAccount()).isEqualTo(new Account(1, 50.0));
80+
assertThat(productService.selectProduct()).isEqualTo(new Product(1, 10.0, 20));
81+
82+
//账户不足
83+
TransactionContext.registerSynchronization(new TransactionSynchronization() {
84+
@Override
85+
public void afterCompletion(int status) {
86+
if (status == STATUS_ROLLED_BACK) {
87+
placeOrderRequest.setOrderStatus(OrderStatus.FAIL);
88+
}
89+
}
90+
});
91+
placeOrderRequest.setAmount(6);
92+
placeOrderRequest.setOrderStatus(OrderStatus.INIT);
93+
assertThrows(RuntimeException.class, () -> orderService.placeOrder(placeOrderRequest));
94+
assertThat(placeOrderRequest.getOrderStatus()).isEqualTo(OrderStatus.FAIL);
95+
assertThat(orderService.selectOrders()).isEmpty();
96+
assertThat(accountService.selectAccount()).isEqualTo(new Account(1, 50.0));
97+
assertThat(productService.selectProduct()).isEqualTo(new Product(1, 10.0, 20));
98+
99+
//正常下单
100+
TransactionContext.registerSynchronization(new TransactionSynchronization() {
101+
@Override
102+
public void afterCommit() {
103+
placeOrderRequest.setOrderStatus(OrderStatus.SUCCESS);
104+
}
105+
});
106+
placeOrderRequest.setAmount(5);
107+
placeOrderRequest.setOrderStatus(OrderStatus.INIT);
108+
assertThat(orderService.placeOrder(placeOrderRequest)).isEqualTo(OrderStatus.INIT);
109+
assertThat(placeOrderRequest.getOrderStatus()).isEqualTo(OrderStatus.SUCCESS);
110+
assertThat(orderService.selectOrders()).isEqualTo(Arrays.asList(new Order(3, 1, 1, 5, 50.0)));
111+
assertThat(accountService.selectAccount()).isEqualTo(new Account(1, 0.0));
112+
assertThat(productService.selectProduct()).isEqualTo(new Product(1, 10.0, 15));
113+
}
114+
115+
private DataSourceProperty createDataSourceProperty(String poolName) {
116+
DataSourceProperty result = new DataSourceProperty();
117+
result.setPoolName(poolName);
118+
result.setDriverClassName("org.h2.Driver");
119+
result.setUrl("jdbc:h2:mem:" + poolName + ";INIT=RUNSCRIPT FROM 'classpath:db/ds-with-transactional.sql'");
120+
result.setUsername("sa");
121+
result.setPassword("");
122+
return result;
123+
}
124+
}
125+
126+
@SpringBootApplication
127+
class DsTransactionalApplication {
128+
public static void main(String[] args) {
129+
SpringApplication.run(DsTransactionalApplication.class, args);
130+
}
131+
}

dynamic-datasource-spring-boot-starter/src/test/java/com/baomidou/dynamic/datasource/fixture/v1/NestDataSourceTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,4 @@ class NestApplication {
8888
public static void main(String[] args) {
8989
SpringApplication.run(NestApplication.class, args);
9090
}
91-
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright © 2018 organization baomidou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.baomidou.dynamic.datasource.fixture.v1.service.tx;
17+
18+
import lombok.AllArgsConstructor;
19+
import lombok.Data;
20+
21+
22+
@Data
23+
@AllArgsConstructor
24+
public class Account {
25+
private Integer id;
26+
27+
/**
28+
* 余额
29+
*/
30+
private Double balance;
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright © 2018 organization baomidou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.baomidou.dynamic.datasource.fixture.v1.service.tx;
17+
18+
import com.baomidou.dynamic.datasource.annotation.DS;
19+
import org.springframework.stereotype.Service;
20+
import org.springframework.util.Assert;
21+
22+
import javax.sql.DataSource;
23+
import java.sql.*;
24+
25+
26+
@Service
27+
@DS("account")
28+
public class AccountService {
29+
private final DataSource dataSource;
30+
31+
public AccountService(DataSource dataSource) {
32+
this.dataSource = dataSource;
33+
}
34+
35+
public void reduceBalance(Integer userId, Double price) {
36+
try (Connection connection = dataSource.getConnection()) {
37+
PreparedStatement preparedStatement = connection.prepareStatement("select * from account where id=?");
38+
preparedStatement.setInt(1, userId);
39+
ResultSet resultSet = preparedStatement.executeQuery();
40+
Account account = null;
41+
if (resultSet.next()) {
42+
Integer id = resultSet.getObject(1, Integer.class);
43+
Double balance = resultSet.getObject(2, Double.class);
44+
account = new Account(id, balance);
45+
}
46+
Assert.notNull(account, "用户不存在");
47+
Double balance = account.getBalance();
48+
if (balance < price) {
49+
throw new RuntimeException("余额不足");
50+
}
51+
double currentBalance = account.getBalance() - price;
52+
String sql = "update account set balance=? where id=?";
53+
PreparedStatement updateStatement = connection.prepareStatement(sql);
54+
updateStatement.setDouble(1, currentBalance);
55+
updateStatement.setInt(2, userId);
56+
updateStatement.executeUpdate();
57+
} catch (SQLException e) {
58+
throw new RuntimeException(e);
59+
}
60+
}
61+
62+
public Account selectAccount() {
63+
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
64+
ResultSet resultSet = statement.executeQuery("SELECT * FROM account where id=1");
65+
Account account = null;
66+
if (resultSet.next()) {
67+
Integer id = resultSet.getObject(1, Integer.class);
68+
Double balance = resultSet.getObject(2, Double.class);
69+
account = new Account(id, balance);
70+
}
71+
return account;
72+
} catch (SQLException e) {
73+
throw new RuntimeException(e);
74+
}
75+
}
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright © 2018 organization baomidou
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.baomidou.dynamic.datasource.fixture.v1.service.tx;
17+
18+
import lombok.AllArgsConstructor;
19+
import lombok.Data;
20+
21+
22+
@Data
23+
@AllArgsConstructor
24+
public class Order {
25+
private Integer id;
26+
27+
/**
28+
* 用户ID
29+
*/
30+
private Integer userId;
31+
/**
32+
* 商品ID
33+
*/
34+
private Integer productId;
35+
/**
36+
* 数量
37+
*/
38+
private Integer amount;
39+
40+
/**
41+
* 总金额
42+
*/
43+
private Double totalPrice;
44+
}

0 commit comments

Comments
 (0)