简体   繁体   中英

Transactional Service Method that updates 2 repositories

I want to test Transactional operation in my project. Basically, I want to roll back the userService.saveUser() operation, if an exception is thrown. I have simplified the classes, and you can find it below.

A user must live in an address. An address can have multiple users.

Address Entity

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Address {

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "STREET")
    @NotNull
    private String street;

    @ToString.Exclude
    @OneToMany(mappedBy = "address")
    private Set<User> users = new HashSet<>();
}

User Entity

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "USER")
public class User {

    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "FIRSTNAME", nullable = false)
    @NotNull
    private String firstName;

    @ManyToOne(fetch = FetchType.LAZY)
    private Address address;

}

Repositories

public interface AddressRepository extends CrudRepository<Address, Long> {
}
public interface UserRepository extends CrudRepository<User, Long> {

}

UserService Class

@Service
@Slf4j
@AllArgsConstructor
public class UserService {
    
    @Autowired
    AddressRepository addressRepository;

    @Autowired
    UserRepository userRepository;

    @Transactional
    public void saveUser(String firstName, String street) {
        var address1 = Address.builder.street(street).build();
        // to make sure that I have "id" of the address when I am saving it.
        var addressSaved = addressRepository.save(address1);
        if ("f1".equals(firstName))
           throw new RuntimeException("some exception");
        var user = User.builder()
                .firstName(firstName)
                .address(addressSaved)
                .build();
        // this operation can also throw DataIntegrityViolationException
        userRepository.save(user);
    }
}

This is my test class

@SpringBootTest
class UserServiceIT {

    @Autowired
    AddressRepository addressRepository;
    @Autowired
    UserRepository userRepository;
    @Autowired
    UserService userService;

    @BeforeEach
    void beforeEach() {
        userRepository.deleteAll();
        addressRepository.deleteAll();
    }
    
    @Test
    void test_saveUser() {
        assertThrows(RuntimeException.class,() -> userService.saveUser("f1", "s1"));
        assertEquals(0, userRepository.count());
        assertEquals(0, addressRepository.count());
    }

    @Test
    void test_saveUser2() {
        // column: nullable = false will throw the exception
        assertThrows(DataIntegrityViolationException.class,() -> userService.saveUser(null, "s1"));
        assertEquals(0, userRepository.count());
        assertEquals(0, addressRepository.count());
    }
    
}

Both of the tests give assertion error on address count (Address is saved and user is not saved). I expect address to be roll backed (and not to be saved) since there is an error after saving the address, and while saving the user (some condition is violated, therefore 2 saves must be roll backed). What am I doing wrong?

application.yml for test environment

spring:
  devtools:
    restart:
      enabled: false
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=false
    driverClassName: org.h2.Driver
    username: sa
    password: 123
  h2:
    console:
      enabled: false
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
    database: H2
    show-sql: false
    hibernate:
      ddl-auto: create

You can reach the whole sample project from this link: https://wetransfer.com/downloads/7cb870266e2e20f610b44d3cc9f229c220220308071438/7b88a2700076a3e53771e389c796cfe420220308071438/c777ab

The code you posted here differs from what is actually exists in the original code you uploaded.

original code:

@Transactional
void saveUser(String firstName, String street) {
    var address = Address.builder().street(street).build();
    var addressSaved = addressRepository.save(address);

    if ("f1".equals(firstName))
        throw new RuntimeException("f1");

    var user = Person.builder()
            .firstName(firstName)
            .address(addressSaved)
            .build();
    personRepository.save(user);
}

This method actually have default access modifier so CGLIB is not able to override it and creates the needed logic. change access modifier of this method to public

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM