How to create Hibernate mapping for the following scenario.
I have three tables, Users, Systems and System Assign.
There are three types of user -- Normal user, super user and admin.
Users Table has five columns -- user-ID , userName, password, email id and userType. userType will decide whether a user is super user or admin or normal user.
Systems table have some column but the most important is systemsID which will be used for mapping.
I have created one table System Assign to assign a system to a user with two columns user-ID and system_id (or it can be corrected if required). Also created these two as the foreign key of respective table.
The conditions of mapping are :
a user can have one or more system id on his name.
a system id can be assign to one or more users.
when a system_assign record is deleted from the UI it should only break the link between the user and system but user and system should be in database.
Also I have to make some database changes like this:
If a super user creates a user , this user will be under him
if a super user creates a system, system will be under him.
if a user is deleted then system should come under super user now.
I need to know how to create hibernate classes for this senario
#Entity
#Table(name = "SYSTEM_ASSIGN")
public class SAssign implements Serializable {
/**
*
*/
private static final long serialVersionUID = -1945028654484806943L;
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USERSSO")
private Users user;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "SYSTEMID")
private Systems system;
public Users getUser() {
return user;
}
public void setUser(Users user) {
this.user = user;
}
public Systems getSystem() {
return system;
}
public void setSystem(Systems system) {
this.system = system;
}
}
I use a list of Role instead of userType and the #ManyToMany association for user and roles. Join tables for the #ManyToMany association will be created by Hibernate. You don't need to use persistent classes for it.
Classes with the mapping: usersystem
A test for mapping: UserSystemTest.java
public enum RoleEnum {
ADMIN, USER, SUPER_USER
}
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue
#Column(name = "f_pid")
private Long pid;
#Column(name = "f_role")
#Enumerated(EnumType.STRING)
private RoleEnum role;
}
#Entity
#Table(name = "systems")
public class SomeSystem {
#Id
#GeneratedValue
#Column(name = "f_pid")
private Long pid;
#Column(name = "f_name")
private String name;
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue
#Column(name = "f_pid")
private Long pid;
#Column(name = "f_user_name")
private String userName;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_roles", joinColumns = { #JoinColumn(name = "fk_user") },
inverseJoinColumns = { #JoinColumn(name = "fk_role") })
private List<Role> roles = new ArrayList<Role>();
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "system_assign", joinColumns = { #JoinColumn(name = "fk_user") },
inverseJoinColumns = { #JoinColumn(name = "fk_system") })
private List<SomeSystem> systems = new ArrayList<SomeSystem>();
}
Related
Suppose you have two resources, User and Account. They are stored in separate tables but have a one-to-one relationship, and all API calls should work with them both together. For example a POST request to create a User with an Account would send this data:
{ "name" : "Joe Bloggs", "account" : { "title" : "My Account" }}
to /users rather than have multiple controllers with separate routes like users/1/account. This is because I need the User object to be just one, regardless of how it is stored internally.
Let's say I create these Entity classes
#Table(name = "user")
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#NotNull
Account account;
#Column(name = "name")
String name;
}
#Table(name = "account")
public class Account {
#OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#NotNull
User user;
#Column(name = "title")
String title;
}
The problem is when I make that POST request above, it throws an error because user_id is missing, since that's required for the join, but I cannot send the user_id because the User has not yet been created.
Is there a way to create both entities in a single API call?
Since it is a bi-directional relation, and one-to-one is a mandatory in this case, you should persist a user entity and only then persist an account. And one more thing isn't clear here is db schema. What are the pk's of entities? I coukd offer to use user.id as a single identity for both of tables. If so, entities would be as:
User(id, name), Account(user_id, title) and its entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "user_id", insertable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, optional = false)
#MapsId
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "user_id")
private Account account;
#Column(name = "name")
private String name;
}
at the service layer you must save them consistently:
#Transactional
public void save(User userModel) {
Account account = user.getAccount();
user.setAccount(null);
userRepository.save(user);
account.setUser(user);
accountRepository.save(account);
}
it will be done within a single transaction. But you must save the user first, coz the user_id is a PK of the account table. #MapsId shows that user's id is used as an account's identity
Another case is when account's id is stored in the user table:
User(id, name, account_id), Account(id, title) and entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "account")
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "account_id", insertable = false, updatable = false)
private Long accountId;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "account_id", referencedColumnName = "id", unique = true)
private Account account;
#Column(name = "name")
private String name;
}
in this case an Account entity will be implisitly persisted while User entity saving:
#Transactional
public void save(User userModel) {
userRepository.save(user);
}
will cause an insertion into the both of tables. Since cascade and orphane are declared, for deletion would be enough to set null for the account reference:
user.setAccount(null);
userRepository.save(user);
I have 4 tables here: Register, User, User roles and User Profile Image
Register and User are mapped my a One to One relationship and a reference of Register is generated in Users table...... This is fine..
Now talking about One to Many Relation between User and the Roles table, it also works perfectly by generating a User table reference in the roles table..
But problem is when working with One to One between User and the Profile Image. Profile Image is not generating reference of User....Why the user reference is not generated in Profile Image table
Register
#Entity
#Getter
#Setter
public class Register {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToOne(cascade = CascadeType.ALL,orphanRemoval = true,mappedBy = "register")
private User user;
}
User
#Entity
#Getter
#Setter
public class User {
#Id
#Column(name = "User_Id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToOne(cascade = CascadeType.ALL,fetch=FetchType.EAGER)
#JoinColumn(name = "user_id")
private UserProfileImage userProfileImage;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private List<UserRoles> userRoles;
}
User Profile Image
#Entity
#Getter
#Setter
public class UserProfileImage {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "name")
private String name;
#Column(name = "type")
private String type;
#Column(name = "picByte", length = 100000)
private byte[] picByte;
public UserProfileImage() {
super();
}
public UserProfileImage(String name, String type, byte[] picByte) {
this.name = name;
this.type = type;
this.picByte = picByte;
}
}
Profile mapping in User class is not correct and in your profile class there is no user field and hence it's not generating the user reference in the profile class.
Also, User to Roles mapping is also not correct, your user class will not populate roles with your mappings.
Try this:
public class User {
...
#OneToOne(cascade = CascadeType.ALL,fetch=FetchType.EAGER)
#JoinColumn(name = "PROFILE_IMAGE_ID") // foreign key column in User table
private UserProfileImage userProfileImage;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER,mappedBy="user")
private List<UserRoles> userRoles;
}
public class UserProfileImage {
...
#OneToOne(mappedBy="userProfileImage")
private User user;
...
}
public class UserRole {
...
#ManyToOne
#JoinColumn(name="USER_ID") // foreign key column in User Role table
private User user;
...
}
I'm trying to figure out what I'm doing wrong with this, but I'm learning hibernate annotations and creating a simple library system. Basically, a book gets checked out by a person, and eventually checked in. Here's how I have it configured:
#Entity
#Table
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(nullable = false)
private String name;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "book") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "user") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
#Table(name = "checkoutsession", uniqueConstraints = {#UniqueConstraint(columnNames={"book", "checkIn"})})
public class CheckOutSession {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name="book", nullable=false)
private Book book;
#ManyToOne
#JoinColumn(name="user", nullable=false)
private User user;
#Column(nullable = false)
private java.sql.Timestamp checkOut;
#Column
private java.sql.Timestamp checkIn;
}
I can't figure out for the life of me what I've got configured incorrectly.
[EDIT]
when I try to pull a book it is selecting everything from checkoutsession join checkoutsession join user and dies saying "Unknown column checkoutsess1_.check_in in 'field list';
[EDIT2]
A little more context, I have a BookDAO that extends JpaRepository and when I call findAll() is what's creating that query.
[EDIT3]
Rest Class:
#RestController
#RequestMapping("rest/books")
public class BookController {
#RequestMapping(method = RequestMethod.GET)
public List findBooks() {
return bookService.getAllBooks();
}
}
Service:
#Component
public class BookService {
private BookDao bookDao;
public List getAllBooks() {
return bookDao.findAll();
}
#Autowired
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
DAO:
public interface BookDao extends JpaRepository<Book, Long> {
}
Thanks for any help!
If I run your code and make JPA generate tables based on the entities it seems to work (at least, it does run).
However, your mappings appear to be odd to me, more specifically the #JoinTable annotation. The #JoinTable annotation is commonly used when you have a join table (eg. checkoutSession in your case), but you don't want to map it because it contains no useful information except the links between those two tables.
In that case, you use the #JoinTable annotation as following:
#ManyToMany
#JoinTable(
name = "checkoutsession", // Name of the join table
joinColumns = #JoinColumn(name = "book"), // The column name in checkoutsession that links to book
inverseJoinColumns = #JoinColumn(name = "user") // The column name in checkoutsession that links to user
)
private List<User> users;
So in this case, you can directly link the Book and User entity without having to create the CheckoutSession entity.
However, in your case your join table also contains two timestamps, if you need those in your application, then you don't have to use #JoinTable but simply use #JoinColumn to link them, for example:
#OneToMany(mappedBy = "book") // The field name in CheckoutSession that links to book
private List<CheckoutSession> checkOutSessions;
This is what you should have in your Book entity. Be aware that in this case we're talking about field names not about column names. You have to enter the name of the field in CheckoutSession that maps back to the Book entity.
For more information about the #JoinTable annotation I recommend you to read this answer: JPA "#JoinTable" annotation or this article.
I have 3 tables:
user: (id,username,password)
role: (id,role)
user_roles: (user_id, role_id)
and the following two Hibernate entities:
User:
#Entity
#Table(name = "user")
public class User implements Serializable{
#Id
#GeneratedValue
private Long id;
private String userName;
private String password;
private boolean active = false;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER )
#JoinTable(name = "user_roles", joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "role_id", referencedColumnName = "id") })
private Set<Role> roles = new HashSet<Role>();
....
Role:
#Entity
#Table (name = "roles")
public class Role implements Serializable {
#Id #GeneratedValue
private Long id;
private String role;
....
When I delete now a User the corresponding role get deleted as well from the "role" table instead to delete just the user row and the user_roles relation. Even if other users are still related to that role. I use the following to delete the user.
#Transactional
public void deleteByName(String userName) {
User user = this.getByName(userName);
Session session = sessionFactory.getCurrentSession();
session.delete(user);
}
Anyone know why and how to solve this?
Thanks
This is the expected behaviour for the current mapping. It is configured by the cascade setting:
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER )
private Set<Role> roles = new HashSet<Role>();
If you do not want the roles to be removed when a user is, you will have to narrow down your cascade settings. Right now you are using ALL. This is equivalent to {PERSIST, REMOVE, REFRESH, MERGE, DETACH}. Decide on which ones you need and remove the others.
There is one thing I did not understand. You say that the roles are removed "Even if other users are still related to that role". This should not be possible.
The relationship of Role and User is a many-to-one, so there can be only one user attached to a role.
I have three entities User, Center and Profile, so a user has different profiles depending on the center he's in. So we have a table which links the three entities called *user_center_profile*.
Then we have another entity, called Permission which is linked to Profile, so a profile has several permissions.
Because the main entity is User, I have it maped like this:
#Entity
#Table(name = "profile")
public class Profile implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idprofile")
private Long idProfile;
#Column(name = "codprofile")
private String codProfile;
#Column(name = "desprofile")
private String desProfile;
#OneToMany
#JoinTable(name = "profile_permission", joinColumns = { #JoinColumn(name = "idProfile") }, inverseJoinColumns = { #JoinColumn(name = "idPermission") })
private List<Permission> permissions = new ArrayList<Permission>();
/* getters and setters */
}
#Entity
#IdClass(CenterProfileId.class)
#Table(name = "user_center_profile")
public class CenterProfile implements Serializable {
#Id
#ManyToOne
private Center center;
#Id
#ManyToOne
private Profile profile;
/* getters and setters */
}
#Embeddable
public class CenterProfileId implements Serializable {
private static final long serialVersionUID = 1L;
#ManyToOne
#JoinColumn(name = "idCenter")
private Center center;
#ManyToOne
#JoinColumn(name = "idProfile")
private Profile profile;
/* getters, setters, equals, hashcode */
}
#Entity
#Table(name = "user")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idUser")
private Long idUser;
#Column(name = "codUser")
private String codUser;
/* other properties */
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_center_profile", joinColumns = { #JoinColumn(name = "idUser") }, inverseJoinColumns = {
#JoinColumn(name = "idCenter"), #JoinColumn(name = "idProfile") })
private List<CenterProfile> centersProfileUser = new ArrayList<CenterProfile>();
#Transient
private Center selectedCenter;
/* getters, setters */
}
The thing is that at certain point I have to collect all the permissions that a certain user has... I tried several things and I got lazy load errors, no session or session closed errors, problem loading simultaneous bags...
I even tried to write a plain SQL query and I got the same error...
I can't see the way to build a DetachedCriteria for this, and I don't know if it will give an exception too..
My app can connect to different "centers", when he logs in he can choose which center he wants to connect to, but also once logged in, he can change centers... So when he changes it, I have to recalculate his permissions... that's why I need to get that list of permissions..
How could I get this done in a proper way?
In the end, I built the query like this:
HashSet<Permission> set = new HashSet<Permission>();
for (CenterProfile cp : usuario.getCentersProfileUsuario()) {
// First we build subcriteria to return all the permissions for a certain profile
DetachedCriteria criteriaProfile = DetachedCriteria
.forClass(Profile.class);
criteriaProfile.add(Restrictions.eq("idProfile", cp.getProfile()
.getIdProfile()));
criteriaProfile.createAlias("permissions", "permission");
criteriaProfile.setProjection(Property.forName("permission.idPermission"));
// Then we build criteria for permissions, which should match the results given by subcriteria
DetachedCriteria criteria = DetachedCriteria
.forClass(Permission.class);
criteria.add(Property.forName("idPermission").in(criteriaProfile));
List<Permission> permissions = getHibernateTemplate().findByCriteria(
criteria);
set.addAll(permissions);
}