I dont know why my List of embeddable objects persists to database despite transaction rollback.
I have my Entity User with List of Embeddable Role.
When I'm persisting user with the same username as already existing in database, i see an exception : "RollbackException: Unable to commit: transaction marked for rollback", which is perfectly fine as i have the unique username column. But I dont know why, despite the rollback, list of roles of that user persists to database.
It's like transaction is working only for entity class and persists list of embedded role to database every time even when it shouldnt ( becouse of rollback).
What am I doing wrong ?
Thanks for any hints
(I'm using OpenJPA 2.3.0)
User Code:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique = true, nullable = false)
private String username;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(
name = "ROLES",
joinColumns = #JoinColumn(name = "OWNER_ID")
)
private List<Role> roles;
private String password;
private boolean enabled;
public User(String username, String password) {
this.username = username;
this.password = password;
this.enabled = true;
roles = new ArrayList<Role>();
Role userrole = new Role();
userrole.setRole("ROLE_USER");
userrole.setUsername(username);
roles.add(userrole);
}
//getters and setters
}
Role code:
#Embeddable
public class Role {
#Column(name="username")
String username;
#Column(name="userrole")
String role;
//getters and setters
}
Related
I am currently making project which should have Many-To-Many relationship between groups and users. I should be able to update an user and add one. I have no troubles with adding an user, it gets saved properly. However, when I try to update user, it gives an error that definitely has something to do with that hibernate tries to save groups information. Here is my code.
User Entity
#Entity
#Table(name = "user")
public class User {
#Id
#Column(name = "email")
private String email;
#Column(name = "firstname")
private String name;
#Column(name = "lastname")
private String surname;
#Column(name = "age")
private int age;
#Column(name = "gender")
private String gender;
#ManyToMany
#JoinTable(
name = "user_group"
, joinColumns = #JoinColumn(name = "email")
, inverseJoinColumns = #JoinColumn(name = "group_id")
)
Group entity
#Entity
#Table(name = "category")
public class Group {
#Id
#Column(name = "group_name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(mappedBy = "groupList")
private List<User> userList = new ArrayList<User>();
Controller save method
#PostMapping("/addUser")
public String addUser(#ModelAttribute("user") User user) {
service.saveUser(user);
return "redirect:/allUsers";
}
Service save method
#Override
#Transactional
public void saveUser(User user) {
userDAO.saveUser(user);
}
DAO save method
#Override
public void saveUser(User user) {
entityManager.merge(user);
}
java.sql.SQLSyntaxErrorException: Unknown column 'group1_.description' in 'field list
The problem was that I altered my column names in DB but forgot to update it in Entity class. Got to be careful next time:)
I have a user class and role class and user role class . Now every time i am trying to add a new user with a set of role which is already existing it throws a Unique error which is correct . But ideally it should not try to save the new role if it already exists . Below i am adding all my tables and save method .
#Table(name = "t_user")
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "email")
private String email;
#Column(name = "first_name")
private String firstName;
#Size(max = 100)
#NotBlank(message = "Last name can not be empty")
#Column(name = "last_name")
private String lastName;
#Column(name = "is_archived")
private Boolean isArchived = false;
#Column(name = "qualification")
private String qualification;
#JsonIgnore
#Column(name="password")
private String password;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "t_user_role", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "id")})
private Set<Role> roles = new HashSet<>();
}
#Data
#Table(name = "m_role")
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name="name")
private String name;
}
#Data
#Table(name = "t_user_role")
#Entity
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
}
method where i am saving the user:
User newUser = new User();
newUser.setFirstName(user.getFirstName());
newUser.setLastName(user.getLastName());
newUser.setEmail(user.getEmail());
newUser.setMobileNumber(user.getPassword());
newUser.setPassword(bcryptEncoder.encode(user.getPassword()));
newUser.setRoles(user.getRoles());
return userRepository.save(newUser);
}
and below is the post request format to create the user:
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"name":"ADMIN"
}]
}
I do not want to insert the role if present which should be ideal . But this is the standard way i find while implementing spring security with roles
Your request is missing id of the role. As id is not present. Spring try to add a new role in role table.
{
"firstName":"first",
"lastName":"name",
"email":"email#gmail.com",
"mobileNumber":"1110122223",
"password":"1234567890",
"roles":[{
"id" : "" // ID must be present here.
"name":"ADMIN"
}]
}
Or from the role -> name, you can fetch Role entity/obj from the role table and set it in User object.
[Update 2]:
#ManyToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
You need to change the cascade type from 'ALL' to 'DETACH'. See: ALL means if you save USER, ROLE will also get saved, if you delete USER, role should also get delete. This is not what we want. You only need to use 'ROLE', not manipulate the 'ROLE' tables record in any way.
On behalf of what I understand, your requirements are:
User entity
Role entity, with each user having multiple roles
If role is passed from client, you want to save the role only if it does not exist in your database, else you want to use the existing role (UPDATE: which as per comments and my opinion, is never an ideal thing to do)
In your case, I would suggest let Spring take care of the User->Roles relationship as follows:
public class User {
... all fields
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ToString.Exclude
private Set<Role> roles = new HashSet<>();
}
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
In Role repository, you would want a method: Optional<Role> findByName(String Name);
In between the layers (preferably in the service layer), try this:
public Map<String, Object> addUser(User user) {
// perform validations
user.getRoles().forEach(role -> {
Role existing = roleRepository.findByName(role.getName())
.orElse(new Role(role.getName())); // using optional to create new role with name passed in from the client
if (existing.getId() != null) user.setRole(existing); // UPDATE: NOT IDEAL
});
... other tasks
userRepository.save(user); // this all saves the correct role
return yourResponseMap;
}
Other notes:
We generally prefer to keep fetches Lazy, instead of Eager. But there are cases when you may need Eager retrieval so it depends on you.
Letting Spring Data JPA handle third tables is better in terms of convenience in my opinion.
org.hibernate.PersistentObjectException: detached entity passed to persist occurs when you're trying to save the role passed in from client directly without loading it on your application (see the service layer method for 'add user').
Check this link, you might find it helpful.
This question already has answers here:
Infinite Recursion with Jackson JSON and Hibernate JPA issue
(29 answers)
Closed 3 years ago.
I really don't know how does the security and spring data jpa work, but when I try to receive other users data from database it's acting like an infinite loop and shows only my account info over 9k times and then after few seconds it crashes in web browser with error SyntaxError: JSON.parse: unterminated string literal at line 1 column 39978 of the JSON data,
My userRepository is a part of my UserDetailsServiceImplementation which is used in spring security as authentication with SQL database. It's work fine, I can log in on my account, but I can't query and view others' data info.
I've got no idea how to bypass it. Maybe it's a security feature to don't get access to other people credentials.
UserRepository
#Repository
public interface UserRepository extends JpaRepository<User, Long>{
public User findByUsername(String username);
#Query("SELECT * FROM user")
public List<User> findAll();
}
Controller
#RestController
#RequestMapping("/v1/api")
public class HomeApiController {
#Autowired
private UserRepository userRepository;
#GetMapping("/users")
public List<User> getUsers() {
return userRepository.findAll();
}
}
User
#Entity
#Table
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String username;
#Column
#JsonIgnore
private String password;
#Column
private boolean enabled;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Authority> authorities;
public User() {
}
then field based constructor + getters and setters
Authority
#Entity
#Table
public class Authority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Long id;
#Column
private String role;
#ManyToMany(mappedBy = "authorities")
private Set<User> user;
public Authority() {
}
field constructor + getters and setters
i expected to query and retrieve all other users in user table in database which is also used to authorize users based on roles in system.
json output shows...
{"id":1,"username":"Admin","enabled":true,"authorities":[{"role":"ROLE_USER","user":[{"id":1,"username":"Admin","enabled":true,"authorities":[{"role":"ROLE_USER","user":[{"id":1,"username":"Admin","enabled":true,"authorities":[{"role":"ROLE_USER","user":[{"id":1,"username":"Admin","enabled":true,"authorities":[{"role":"ROLE_USER","user":
and it's infinite nested.
i think there's something wrong with authorities
when i clear user_role table then output works great
id 1
username "Admin"
enabled true
authorities []
what's wrong?
you have to annotate the User set in the Authority class with #JsonIgnore or Annotate the Authorities set in the User class depending on what you need in your API call.
#Entity
#Table
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String username;
#Column
#JsonIgnore
private String password;
#Column
private boolean enabled;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Authority> authorities;
public User() {
}
Or
#Entity
#Table
public class Authority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Long id;
#Column
private String role;
#JsonIgnore
#ManyToMany(mappedBy = "authorities")
private Set<User> user;
public Authority() {
}
EDIT: Also I do recommend using DTOs to minimize the coupling and avoid such problems
I have two tables I need to insert in to in Hibernate - I have a User and every user belongs is a Member. Therfore when creating a new user I need a new entry in the Member table. I have attempted this via creating a Member object which maps to my member table and then having that as a field in my User object which maps to the user table
#Entity
#Table(name = "USER")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "fullName")
private String fullName;
//other fields ommited
#OneToOne
#JoinColumn(name = "id")
private Member member;
My member pojo looks as follows
#Entity
#Table(name = "MEMBER")
public class Member
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String username;
Trying to save the object i do as follows;
#Override
public boolean addUser(User user)
{
if (user == null)
{
throw new IllegalArgumentException("Unable to add null user");
}
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.save(user);
session.getTransaction().commit();
return true;
}
This gives me the row saved in my user table but the entry is not inserted in to the member table. I think my linking annotations are probably incorrect but I am not too sure - please could someone provide some assistance.
Thanks
Try to set the cascade value of the #OneToOne annotation:
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "id")
private Member member;
First thing in your user class you should change the joinColumn to member_id.
As mentioned in another answer to persist a related entity you need to set the cascade to persist, i would recommend using cascade All which will involve the related entity in all operations check the doc
https://docs.oracle.com/cd/E19798-01/821-1841/bnbqm/index.html
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "member_id")
private Member member;
I'm currently taking a look into Hibernate (with Spring, if important) and I tried to make a small web application in which someone can register and log in. There is a secured area which is protected by Spring Security and I already got my UserDetailsService working, so Spring Security is using the Hibernate stuff to do the login.
Now I am working on the registration for new users. I thought about how to do it and I came to having a separate table for the activations which would basically have 3 columns: activationId, username and activation_code. The activation code would be some kind of hash but I guess its not relevant for this question.
My question basically is, how to do the relationship between my users table and the activations. I need to link the username from the activations to the username from the users table.
Do I have to use a One To One Mapping? Or how can I link exactly one column from another table into the activation table?
Here's my User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "username", unique = true, nullable = false, length = 45)
private String username;
#Column(name = "password", nullable = false, length = 64)
private String password;
#Column(name = "enabled", nullable = false)
private boolean enabled;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<UserRole> userRole = new HashSet<UserRole>(0);
#Column(name = "name")
private String name;
#Column(name = "lastname")
private String lastname;
public User() {
}
public User(String username, String password, boolean enabled) {
this.username = username;
this.password = password;
this.enabled = enabled;
}
public User(String username, String password, boolean enabled, Set<UserRole> userRole) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.userRole = userRole;
}
// Getters and setters for all attributes omitted
}
If you need the UserRole Entity too, just tell me and I will add it but I think it's not needed. Here is also the current version of my UserActivation Entity, but of course it is not finished yet:
import javax.persistence.*;
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#Column
private String userName; // here we need some magic relationship
}
You can use the ManyToOne mapping :
#Entity
#Table(name = "activations")
public class UserActivation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long activationId;
#Column(length = 64)
private String activationCode;
#ManyToOne(optional=false)
#JoinColumn(name="USER_NAME")
private User user; // since your User Id is username, the join column will be username
}
if you want the username to be unique, wich means one activation for a single user :
#Entity
#Table(name = "activations",uniqueConstraints=#UniqueConstraint(columnsnames="USER_NAME"))