Line 17 and 24 @Bean(name=”datasourceX”) : To define that each datasource from each method can be referred with @Qualifier(“datasourceX”) in DAO classes

Line 18 and 25 @ConfigurationProperties(“databaseX.datasource”) : To define that each method will get properties (in application.properties) with different prefix to create Datasource

Line 19 @Primary : To define that this datasource is the default datasource.

Line 30 and 38 @Bean(name=”tmX”) : To define that each DataSourceTransactionManager from each method can be referred as @Transactional(“tm2”) in UserService2 class.

Line 32 @Primary: To define that this DataSourceTransactionManager is the default. So we can use @Transactional instead of @Transactional(“tm1”)

Line 33 and 40 @Qualifier(“datasourceX”) : To define which datasource is linked each DataSourceTransactionManager .

Sum up the relations:

“database1.datasource” prefix -> “datasource1” -> “tm1”

“datasource1” will be used with DataSourceTransactionManager “tm1”

“datasource1” will be used in UserDao1 as @Qualifier(“datasource1”)

DataSourceTransactionManager “tm1” will be used in UserService1 as @Transactional

DataSourceTransactionManager “tm2” will be used in UserService2 as @Transactional(“tm2”)

So, @Transactional is the annotation to control db transaction of the first database and @Transactional(“tm2”) is the annotaton to control db transaction of the second database

In UserDao.java, you will see this :

1

2

3

@Qualifier("datasource1")

@Autowired

privateDataSource dataSource;

LIne 1 @Qualifier(“datasource1”) : To define which datasource is linked to.

Similar as UserDao2.java , it is “datasource2” :

1

2

3

@Qualifier("datasource2")

@Autowired

privateDataSource dataSource;

In UserService1.java :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

packagecom.surasint.example.service;

import com.surasint.example.db.UserBean;

import com.surasint.example.db.UserDao1;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

import java.util.List;

@Component

publicclassUserService1{

@Autowired

privateUserDao1 userDao;

@Autowired

privateUserService2 userService2;

@Transactional

publicList<UserBean>list(){

returnuserDao.list();

}

@Transactional

publicIntegerinsertOK(){

UserBean test=newUserBean();

test.setUsername("username"+newDate().getTime());

returnuserDao.insert(test);

}

@Transactional

publicStringinsertBothOK(){

UserBean test=newUserBean();

test.setUsername("username"+newDate().getTime());

Stringresult="";

result=""+userDao.insert(test);

result=result+" and "+userService2.insertOK();

returnresult;

}

@Transactional()

publicvoidinsertBothNotOK(){

UserBean test=newUserBean();

test.setUsername("username"+newDate().getTime());

userDao.insert(test);

userService2.insertNotOK();

//this code will never be reached

thrownewRuntimeException("Test error while trying to insert both database");

}

}

Important points:

Line 16 – 17 :

1

2

@Autowired

privateUserDao userDao;

It is using UserDao1.

Line 18 – 19 :

1

2

@Autowired

privateUserService2 userService2;

It is also using UserService2, which is used in the insertBotNotOK() method. I will explain later why we should use UserService2 instead of UserDao2.

Line 21 , 26 , 33 , 44 : @Transactional , Now that we can use @Transactional instead of @Transactional(“tm1”). That is because we declare @Primary in DataSourceConfiguration.java, so Spring know which DataSourceTransactionManager is for @Transactional

Line 44 – 52 :

1

2

3

4

5

6

7

8

9

10

@Transactional()

publicvoidinsertBothNotOK(){

UserBean test=newUserBean();

test.setUsername("username"+newDate().getTime());

userDao.insert(test);

userService2.insertNotOK();

//this code will never be reached

thrownewRuntimeException("Test error while trying to insert both database");

}

In this method , we want to test rollback. userService2.insertNotOK() will always throw an exception. Note that we insert to the second database via UserService2, not UserDao2. The reason is that we have to use @Transactional(“tm2”) in UserService2 to control db transaction for the second database. If we use UserDao2 here, it will rollback only the first database, but not the second database because there is not transaction control is declared.

In UserService2.java :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

packagecom.surasint.example.service;

import com.surasint.example.db.UserBean;

import com.surasint.example.db.UserDao2;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

import java.util.List;

@Component

publicclassUserService2{

@Autowired

privateUserDao2 userDao2;

@Transactional("tm2")

publicList<UserBean>list(){

returnuserDao2.list();

}

@Transactional("tm2")

publicIntegerinsertOK(){

UserBean test=newUserBean();

test.setUsername("username"+newDate().getTime());

returnuserDao2.insert(test);

}

@Transactional("tm2")

publicvoidinsertNotOK(){

UserBean test=newUserBean();

test.setUsername("username"+newDate().getTime());

userDao2.insert(test);

thrownewRuntimeException("Test error while trying to insert both database");

}

}

Important points:

Line 15 – 16 :

1

2

@Autowired

privateUserDao2 userDao2;

It is using UserDao2

Line 18 , 23 , 30 :

1

@Transactional("tm2")

It is using DataSourceTransactionManager “tm2” which is declared in DataSourceConfiguration.java.

In TestRestController.java :

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

packagecom.surasint.example.web.api;

import com.surasint.example.db.UserBean;

import com.surasint.example.service.UserService1;

import com.surasint.example.service.UserService2;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController

publicclassTestRestController{

privatestaticinti;

@Autowired

UserService1 userService1;

@Autowired

UserService2 userService2;

@GetMapping("/api/user1/list")

publicList<UserBean>list(){

returnuserService1.list();

}

@GetMapping("/api/user1/insertOK")

publicIntegerinsertOK(){

returnuserService1.insertOK();

}

@GetMapping("/api/user2/list")

publicList<UserBean>list2(){

returnuserService2.list();

}

@GetMapping("/api/user2/insertOK")

publicIntegerinsertOK2(){

returnuserService2.insertOK();

}

@GetMapping("/api/user/insertBothOK")

publicStringinsertBothOK(){

returnuserService1.insertBothOK();

}

@GetMapping("/api/user/insertBothNotOK")

publicStringinsertBothNotOK(){

userService1.insertBothNotOK();

return"never happen";

}

}

There is nothing special, each mapping just call method in UserService1 or UserService2.

Try:

create two databases named “testdb” and “testdb2” and run dbscript.sql to those databases in MySql: