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
Related
I am trying to list all records form a user table.
each time I send the request I get this log
Hibernate:
select
u1_0.id,
u1_0.creation_date,
u1_0.email,
u1_0.full_name,
u1_0.password,
u1_0.updated_date,
u1_0.username
from
user u1_0
but this query is not returning any results
this is the result I get from postman http request
[
{},
{},
{},
{},
{}
]
I have a user entity like this:
#Table(name = "user")
#Entity
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#GeneratedValue
private Long id;
private String fullName;
private String username;
private String password;
private String email;
private Date creationDate;
private Date updatedDate;
}
a repository like this:
public interface UserRepository extends JpaRepository<User, Long> {
}
a controller like this:
#RestController
#RequestMapping("/api/user")
public class UserController {
#Autowired
private UserRepository userRepository;
#RequestMapping("/list")
public List<User> listAll(){
return userRepository.findAll();
}
}
and this is my table
Im trying to save data to the database, but instead of it JPA is saving null to the database, I am usually doing it with dto, but since it s a very small project, I want to do it without it
Entity
#Setter
#Getter
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String department;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_COURSE_TABLE",
joinColumns = {
#JoinColumn(name="student_id",referencedColumnName = "id")
}, inverseJoinColumns = {
#JoinColumn(name = "couse_id",referencedColumnName = "id")
})
#JsonManagedReference
private Set<Course> courses;
}
DAO
#Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
}
Controller
#RestController
#RequestMapping("/api")
public class StudentCourseController {
private final StudentRepository studentRepository;
private final CourseRepository courseRepository;
public StudentCourseController(StudentRepository studentRepository,
CourseRepository courseRepository) {
this.studentRepository = studentRepository;
this.courseRepository = courseRepository;
}
#PostMapping
public Student addStudent (Student student){
return studentRepository.save(student);
}
}
and in my application.properties
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialec
t
Make sure is deserialization correct in controller method "addStudent" - If You want to pass Student entity in request body, add annotation #RequestBody to method parameter like:
#PostMapping
public Student addStudent (#RequestBody Student student){
return studentRepository.save(student);
}
If You do not do that - there is possibility to null/empty parameter, what can lead to saving nulls into db.
By the way:
Consider using DTO or Request classes to pass entity in/out your REST application - it will help you avoid circular reference in future and problems with de/serialization your entity.
Consider using ResponseEntity instead of returning object to output - method with ResponseEntity should be like:
#PostMapping
public ResponseEntity<Student> addStudent (#RequestBody Student
student){
return ResponseEntity.ok(studentRepository.save(student));
}
Issue
I'm writing test code for a simple service layer using jpa data entity, but entity's relationship doesn't seem to work properly.
I googled but couldn't find an answer. I'm not sure what's wrong.
Please check the code below
Codes
(0) test property
spring.datasource.url=jdbc:h2:mem:testdb;DATABASE_TO_UPPER=false;MODE=MySQL
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
(1) entities
#Entity
#Getter
#Setter
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public long id;
private String username;
private String password;
#OneToMany(mappedBy = "user")
private List<Authority> authList;
public Set<String> getAuthName() {
if (this.authList == null || this.authList.isEmpty()) return null;
Set<String> authNameSet = new HashSet<>();
authList.forEach(e -> authNameSet.add(e.getStringName()));
return authNameSet;
}
}
//----------
#Entity
#Getter
#Setter
#Table(name = "authorities")
public class Authority implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#Enumerated(EnumType.STRING)
#Column(length = 10)
private AuthorityName name;
}
(2) repositories
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
}
//----------
#Repository
public interface AuthorityRepository extends CrudRepository<Authority, Long> {
List<Authority> findByUser(User user);
}
(3) service layer to test
#Service
public class AuthorityService {
// this is the method i want to test
public boolean isUserHaveAuthorityOf(User user, AuthorityName authOf) {
if (user == null || authOf == null) throw new InternalError("msg");
if (user.getAuthName() == null) return false;
return user.getAuthName().contains(authOf.name());
}
}
(4) test code
import static org.junit.jupiter.api.Assertions.*;
#TestPropertySource("/application-test.properties")
#SpringBootTest
#Transactional
class AuthorityServiceTest {
#Autowired private UserRepository userRepository;
#Autowired private AuthorityRepository authorityRepository;
#Autowired private AuthorityService authorityService;
private User sampleUser;
#BeforeEach
void setupDB() {
User user = new User();
user.setId(1);
user.setUsername("name");
user.setPassword("passwd");
user.setEmail("email#dot.com");
user.setBirth("20000101");
user.setAddress("addr");
user.setAddressDetail("addrDetail");
user.setZipCode("12345");
user.setDeleted(false);
user.setDisabled(false);
user.setBlock(false);
userRepository.save(user);
sampleUser = user;
}
#Test
void testIsUserHaveAuthorityOfNormalCase() {
assertFalse(authorityService.isUserHaveAuthorityOf(sampleUser, AuthorityName.USER));
assertFalse(authorityService.isUserHaveAuthorityOf(sampleUser, AuthorityName.ADMIN));
Authority auth = new Authority();
auth.setUser(sampleUser);
auth.setName(AuthorityName.USER);
authorityRepository.save(auth);
assertNotNull(sampleUser.getAuthName()); // it is null!
// I thought it was because of a caching, so I get a new user from the db.
User flushedUser = userRepository.findById(sampleUser.getId()).get();
assertNotNull(flushedUser.getAuthName()); // but still null
}
#AfterEach
void cleanDB() {
authorityRepository.deleteAll();
userRepository.deleteAll();
}
}
Thank you
You have a bidirectional relationship between User and Authority so you should keep the relationship in sync. As well as setting the User in an Authority you should also add the Authority to the the User
public void addAuthority(Authority authority) {
if (authList == null) {
authList = new ArrayList<>();
}
authList.add(authority);
authority.setUser(this);
}
It would also be helpful to see how you are persisting the sampleUser
I want to make login form with data from my own database. I made Entity User class:
#Entity
#Table(name = "Auth_data")
public class User {
#Id
#GeneratedValue
#Getter #Setter
private Long id;
#Column(name= "username")
#Getter #Setter
private String username;
#Column(name="password")
#Getter #Setter
private String password;
#Enumerated(EnumType.ORDINAL)
#Column(name="role")
#Getter #Setter
private Role role;
#Column(name = "client_ID")
#Getter #Setter
private Long clientID;
#Column(name = "instructor_ID")
#Getter #Setter
private Long instructorID;
public User() {}
public User(String username, String password, Role role, Long clientID, Long instructorID) {
this.username = username;
this.password = password;
this.role = role;
this.clientID = clientID;
this.instructorID = instructorID;
}
}
Also I made UserDetailsServiceIml class which implements UserDetailsService:
#Service
public class UserDetailsServiceIml implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final User user = userRepository.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException(username);
}
Set<GrantedAuthority> grantedAuthorities = new HashSet< >();
grantedAuthorities.add(new SimpleGrantedAuthority(user.getRole().name()));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
grantedAuthorities);
}
}
My User repository class:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
I also tried make Query like this:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Query("Select u from User u WHERE u.username=:username")
User findByUsername(#Param("username") String username);
}
But when I enter login and password into login form I get error, because User is Null after searching in database. How can I fix it?
UPD: SOLVED!
I don't know why and how. I found that hibernate created new table "auth_data", but not used my table "Auth_data". So, I renamed "Auth_data" to "auth_data" and now it's work! I also changed #Getter #Setter on #Data,changed in User repository on " User findByUsername(String username);" and changed type of field role from enum to String.
It is possible that you don't seed the database with the user you are trying to login. You can use an Application Runner class to seed it on every start if the user doesn't exist in the database. Also, you can apply the #Data annotation from Lombok on class level to generate getters and setters for all the fields.
package com.javahowtos.dataseeddemo.dataseed;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.javahowtos.dataseeddemo.model.User;
import com.javahowtos.dataseeddemo.repository.UserRepository;
#Component
public class UserDataLoader implements CommandLineRunner {
#Autowired
UserRepository userRepository;
#Override
public void run(String... args) throws Exception {
loadUserData();
}
private void loadUserData() {
if (userRepository.count() == 0) {
User user1 = new User("John", "Doe");
User user2 = new User("John", "Cook");
userRepository.save(user1);
userRepository.save(user2);
}
System.out.println(userRepository.count());
}
}
Source: https://javahowtos.com/guides/107-spring/376-how-to-seed-the-database-in-spring-boot-project.html
You have to be sure that you have a user with the appropriate username in the database (case sensitive), if you do not have a user you can insert some test users (with the migration tool, with custom component, or manually)
You probably need to add a unique index for username
If you extend JpaRepository and add method findByUsername spring will generate implementation and you do not need #Query, if you have multiple items you can use findFirstByUsername
A guy ask me for help him resolve a strange bug in security service of his spring boot app, after hours of trail I manage to fix the bug but I really have no idea what happen. Please look at these classes:
Class User : user infomation in database
#Getter
#Setter
#Entity
#Table(name = "user", uniqueConstraints = {#UniqueConstraint(columnNames = {"user_name"})})
public class User extends DateAudit implements Serializable {
// Id, username, password and constructor... not really important
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> role = new HashSet<>();
}
Class UserDto : implement Spring's interface for manage user credential
#Getter
#Setter
#AllArgsConstructor
public class UserDto implements org.springframework.security.core.userdetails.UserDetails {
private long id;
private String Username;
#JsonIgnore
private String password;
private Collection<? extends GrantedAuthority> authorities;
public static UserDto create(User user) {
List<GrantedAuthority> authorities = user.getRole().stream()
.map(role -> new SimpleGrantedAuthority(role.getName().name())).collect(Collectors.toList());
return new UserDto(user.getId(), user.getUserName(), user.getPassword(), authorities);
}
// implement interface's methods, only getters, not important
}
Class CustomUserService : Auth service
#Service
public class CustomUserService implements org.springframework.security.core.userdetails.UserDetailsService {
private final UserRepository userRepository;
#Autowired
public CustomUserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
#Transactional
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user = userRepository.findUserByUserName(userName).orElseThrow(() -> {
return null;
});
return UserDto.create(user);
}
}
Full source code can be found here (it is only a simple spring boot app with some classes) : https://github.com/raizo000/admin_project (his repo uses embedded tomcat, I've tried to change it to jetty but it is not the cause)
When I run code the first time with these 3 classes. The line
return UserDto.create(user);
Give me a NoClassDefFoundError :
WARN 7252 --- [qtp223566397-22] org.eclipse.jetty.server.HttpChannel : /login
java.lang.NoClassDefFoundError: com/example/admin/dto/UserDto
at com.example.admin.services.CustomUserService.loadUserByUsername(CustomUserService.java:28) ~[classes/:na]
at com.example.admin.services.CustomUserService$$FastClassBySpringCGLIB$$b9234a59.invoke(<generated>) ~[classes/:na]
....
I have checked the jar file, there is a UserDto.class file in the right directory.
Remove the #Transactional help fix the error but cause another lazily initialize error and I end up change fetch = FetchType.LAZY in User class to fetch = FetchType.EAGER as a quick fix.
Why adding Transactional can cause a NoClassDefFoundError? Do remove Transactional is the right solution or there is a better fix?
When a class is not found, the first thing that you should make is clean and build.
Because, the last build is saving some dependency.
So the #Transactional #interface was not found in some corrupt package.
A rebuild(clean build) solves it.