I was going through some tutorials on Hibernate, and I came across one scenario in which a primary key can be a compound (like 2-3 fields taken together form primary key). As per my understanding, this is achievable using #EmbeddedId annotation.
I tried to do this using simple application.
UserObj Object:
package com.vipin.model;
import javax.persistence.Embeddable;
#Embeddable
public class UserObj {
private int userId;
private String userName;
private String SSN;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getSSN() {
return SSN;
}
public void setSSN(String sSN) {
SSN = sSN;
}
}
****UPDATE*****
UserModel which uses UserObj as Compound primary key:
#Entity(name="USER_DETAILS")
public class UserModel implements Serializable {
#EmbeddedId
private UserObj userObj;
#Id
#Column(name="USER_ID")
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private int userId;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public UserObj getUserObj() {
return userObj;
}
public void setUserObj(UserObj userObj) {
this.userObj = userObj;
}
Main application/ demo application:
package com.vipin.mainapplication;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import com.vipin.model.UserModel;
import com.vipin.model.UserObj;
public class HibernateMain {
public static void main(String[] args) {
UserModel userModel = new UserModel();
UserObj userObj = new UserObj(); //Demo of Primary key which is an object.
userObj.setUserId(1);
userObj.setUserName("VIPIN KOUL");
userObj.setSSN("000001");
userModel.setUserObj(userObj); //Demo of Primary key which is an object.
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(userModel);
session.getTransaction().commit();
session.close();
sessionFactory.close();
}
}
However when I run this program, i am able to save the data into DB, even though i supply the same values for all these fields. With #EmbeddedId, isn't hibernated supposed to check that it doesn't allow to save this object into DB if same object exists in DB (i.e. duplicate values not allowed).
Do we need to enforce anything on DB side as well? I am under the impression that this annotation is going to check.
Any clues greatly appreciated.
***UPDATE****
I missed to include one more piece of code in UserModel:
#Id
#Column(name="USER_ID")
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private int userId;
With this, hibernate was taking the combination of these four coloumns as primary key, and so no exception.
I removed this field and when i run the program second time (with same values), it throw the exception.
Hence, in conclusion #EmbeddedId does enforce primary key in case we have compound primary key.
Related
i am writing a web application(server based application) where i am having a dao layer, the service layer and the application layer. how should i take over the lazy initialization exception, caused due to the fact that entity returned from dao layer is concerned with the session opened inside the method from where it is returned and also closed there which makes the entity detached.
Next thing is it safe to share the hibernate entities across different layer. what makes me to ask this question is the scenario: for example suppose i am having a hibernate entity having one to one association with some other entity. and suppose dao passed it to the service layer to the application layer. now if i try to get this associated entity in application layer through the passed entity getter method, a database query is fired which i think is messing up with the "seperation of concerns" as database related operation should be constrained to the dao layer. am i right?
i have discovered the mentioned problem during the time i am unit testing my dao layer through in-memory database. My scenario is, i am having one of the pojo class called RegisteredUser having the fields: (id, username, firstname, lastname, passwHash, email, StudyCentre). StudyCentre is an another entity which is assosciated with RegistereUser by one to one mapping and username is the naturalid.
What i want is 2 types of read operation, first one is i need to get user details without studycentre through natural id and second one is getting the complete user fields again through naturalid. is making two seperate DTOs a good idea here and passing them across layers.
RegisteredUser Entity:
package com.ignoubadhega.pojos;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.NaturalId;
#Entity
#Table(name = "registered_user")
#DynamicUpdate
public class RegisteredUser {
private Long dbUserId;
private String userName;
private String passwHash;
private String firstName;
private String lastName;
private String email;
private StudyCentre studyCentre;
RegisteredUser() {
}
public RegisteredUser(
String userName, String passwHash, String firstName,
String lastName, String email
) {
super();
this.userName = userName;
this.passwHash = passwHash;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "db_user_id")
public Long getDbUserId() {
return dbUserId;
}
#Override
public String toString() {
return "RegisteredUser [dbUserId="
+ dbUserId
+ ", userName="
+ userName
+ ", passwHash="
+ passwHash
+ ", firstName="
+ firstName
+ ", lastName="
+ lastName
+ ", email="
+ email
+ "]";
}
public void setDbUserId(Long dbUserId) {
this.dbUserId = dbUserId;
}
#Column(name = "username", nullable = false, unique = true)
#NaturalId
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
#Column(name = "passw_hash", nullable = false)
public String getPasswHash() {
return passwHash;
}
public void setPasswHash(String passwHash) {
this.passwHash = passwHash;
}
#Column(name = "first_name", nullable = false)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#Column(name = "last_name", nullable = false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#Column(name = "email", nullable = false, unique = true)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "db_study_centre_id", nullable = false)
public StudyCentre getStudyCentre() {
return studyCentre;
}
public void setStudyCentre(StudyCentre studyCentre) {
this.studyCentre = studyCentre;
}
}
Dao Implementor:
package com.ignoubadhega.dao.impl;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import com.ignoubadhega.dao.RegisteredUserDAO;
import com.ignoubadhega.pojos.RegisteredUser;
public class RegisteredUserDAOImpl implements RegisteredUserDAO {
private SessionFactory sessionFactory;
public RegisteredUserDAOImpl(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
#Override
public void addUser(RegisteredUser user) {
try (Session session = sessionFactory
.openSession()) {
session.beginTransaction();
session.persist(user);
session.getTransaction().commit();
} catch (HibernateException except) {
except.printStackTrace();
}
}
#Override
public RegisteredUser getUserByUserName(String username, boolean doesStudyCentereNeeded) {
try (Session session = sessionFactory
.openSession()) {
RegisteredUser user = session
.bySimpleNaturalId(RegisteredUser.class).load(username);
if (doesStudyCentereNeeded) {
user.setStudyCentre(user.getStudyCentre());
}
return user;
} catch (HibernateException except) {
except.printStackTrace();
}
return null;
}
#Override
public void deleteUser(RegisteredUser user) {
try (Session session = sessionFactory
.openSession()) {
session.beginTransaction();
session.delete(user);
session.getTransaction().commit();
} catch (HibernateException except) {
except.printStackTrace();
}
}
#Override
public void updateUser(RegisteredUser user) {
try (Session session = sessionFactory
.openSession()) {
session.beginTransaction();
session.update(user);
session.getTransaction().commit();
} catch (HibernateException except) {
except.printStackTrace();
}
}
}
TestCase which finds the problem of lazy initalization:
#Test
#DisplayName(
"User through its natural id 'username' assuming the user"
+ " is persistent in the database is successful"
)
void test_fetching_a_persistent_user_through_username_is_successful() {
try (Session session = sessionFactory.openSession()) {
session.beginTransaction();
session.persist(user);
session.getTransaction().commit();
RegisteredUser retrievedUser =
dao.getUserByUserName("prav", true);
assertNotNull(retrievedUser);
assert_actual_user_and_retrieved_user_fields_are_equal(user,
retrievedUser);
} catch (HibernateException except) {
except.printStackTrace();
}
}
private static void assert_actual_user_and_retrieved_user_fields_are_equal(
RegisteredUser actualUser, RegisteredUser userRetrieved
) throws MultipleFailuresError {
assertAll("user fields",
() -> assertEquals(actualUser.getUserName(),
userRetrieved.getUserName()),
() -> assertEquals(actualUser.getPasswHash(),
userRetrieved.getPasswHash()),
() -> assertEquals(actualUser.getFirstName(),
userRetrieved.getFirstName()),
() -> assertEquals(actualUser.getLastName(),
userRetrieved.getLastName()),
() -> assertEquals(actualUser.getEmail(),
userRetrieved.getEmail()),
() -> {
StudyCentre retrievedCentre =
userRetrieved.getStudyCentre();
assertNotNull(retrievedCentre);
assertAll("user study centre assosciated",
() -> assertEquals(
actualUser.getStudyCentre().getData()
.getStudyCentreName(),
retrievedCentre.getData()
.getStudyCentreName()),
() -> assertEquals(
actualUser.getStudyCentre().getData()
.getRegionalCentreCode(),
retrievedCentre.getData()
.getRegionalCentreCode()));
});
}
i want to keep my service layer(not yet implemented) to be isolated from things specific to hibernate like sessions and database related operations(CRUD). how can i achieve it. is there any design patterns i should follow. i am new to hibernate. please guide me if i am doing something wrong any where. i have tried finding the similar threads on google but failed to get any insights about the issue.
how should i take over the lazy initialization exception, caused due to the fact that entity returned from dao layer is concerned with the session opened inside the method from where it is returned and also closed there which makes the entity detached.
You would deal with that by opening and closing the session in the service or the application layer, and doing all the work in a single transaction.
is it safe to share the hibernate entities across different layer
Yes. What is not safe is to use an entity instance across several threads, because entities are not thread-safe.
a database query is fired which i think is messing up with the "seperation of concerns" as database related operation should be constrained to the dao layer. am i right?
No. The service layer doesn't contain any code to trigger this database query. It happens transparently, without the service layer having to care about it, and because you chose to make the association lazy.
is making two seperate DTOs a good idea here and passing them across layers.
No. DTOs are useful to transfer data between separate applications. Inside your application, working with managed entities is the correct way.
i want to keep my service layer(not yet implemented) to be isolated from things specific to hibernate like sessions and database related operations(CRUD). how can i achieve it.
By using Spring or Java EE (or any other framework that has this feature) which allow using declarative transactions and deal with the task of opening/closing sessions and transactions for you whenever a transactional method is called.
You should also avoid using the proprietary Session API, and use the standard JPA API instead.
I want to establish one to many relation between table vendor detail and product detail. like one vendor can have multiple products. but when i am inserting data into table its inserting all the four fields but not mapping vendorid into ProductDetail Table
and query generated is this.
Hibernate: insert into ProductInfo (productCategory, productDetails, productPrice, VendorId) values (?, ?, ?, ?) It shuld map vendor ID also but in table its empty.
VendorDetail.java
package com.cts.entity;
import javax.persistence.*;
#Entity
#Table(name = "VendorInfo")
public class VendorDetails {
#Id
#Column
private Long VendorId;
#OneToMany
private ProductDetails productdetail;
#Column
private String VendorName;
#Column
private String Password;
public String getVendorName() {
return VendorName;
}
public void setVendorName(String vendorName) {
VendorName = vendorName;
}
public Long getVendorId() {
return VendorId;
}
public void setVendorId(Long vendorId) {
VendorId = vendorId;
}
public String getPassword() {
return Password;
}
public void setPassword(String password) {
Password = password;
}
}
ProductDetails.java
package com.cts.entity;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
#Entity#Table(name = "ProductInfo")
public class ProductDetails {
#ManyToOne(cascade = CascadeType.ALL)#JoinColumn(name = "VendorId")
private VendorDetails vendordetails;
public ProductDetails() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int productId;
#Column
private String productCategory;
#Column
private String productDetails;
#Column
private String productPrice;
public VendorDetails getVendordetails() {
return vendordetails;
}
public void setVendordetails(VendorDetails vendordetails) {
this.vendordetails = vendordetails;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public String getProductCategory() {
return productCategory;
}
public void setProductCategory(String productCategory) {
this.productCategory = productCategory;
}
public String getProductDetails() {
return productDetails;
}
public void setProductDetails(String productDetails) {
this.productDetails = productDetails;
}
public String getProductPrice() {
return productPrice;
}
public void setProductPrice(String productPrice) {
this.productPrice = productPrice;
}
}
DAO class ProductDetailDaoImpl.java
package com.cts.Dao;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.cts.entity.ProductDetails;
import com.cts.entity.to.ProductDetailsTo;
#Repository
public class ProductDetailDaoImpl implements ProductDetailDao {
#Autowired
SessionFactory sessionFactory;
#Transactional
public boolean saveProductInfo(ProductDetailsTo productTo) {
System.out.println("M in Registration DAO");
System.out.println(productTo.getProductCategory());
System.out.println(productTo.getProductDetails());
System.out.println(productTo.getProductId());
System.out.println(productTo.getProductPrice());
//getting productTo data to entity class
ProductDetails prodet = productTo.getEntity();
System.out.println("Value of product details is:" + prodet.getProductDetails());
sessionFactory.getCurrentSession().save(prodet);
return false;
}
}
VendorDetails has many ProductDetails so you need to make one to many annotation like this:-
#OneToMany(mappedBy="vendordetails") //mappedBy value will be what you declared //in ProductDetails class.
private Collection<ProductDetails> productdetail=new ArrayList<ProductDetails>;
and create the setter and getter of this.
Now in ProductDetails class you need to annotate many to one like this:-
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "VendorId")
private VendorDetails vendordetails;
Then a new column named 'VendorId' will be create in table 'ProductInfo' and since declare mappedBy value="vendordetails" so each vendor id would be insert.
I think you should replace the code
#OneToMany
private ProductDetails productdetail;
to
#OneToMany
private Set productdetailSet;
And create setter and getter for this.
You can visit the blog http://gaurav1216.blogspot.in/2014/01/hibernate-tutorial-day-5.html for one to many using annotation.
Here's my User:
package models.user;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import play.data.validation.Constraints.MaxLength;
import play.data.validation.Constraints.MinLength;
import play.data.validation.Constraints.Required;
import play.db.ebean.Model;
#Entity
#Table(name = "T_USER")
public class User extends Model {
#Id
public Long id;
#Required
#MaxLength(30)
#MinLength(4)
public String username;
#Required
#MaxLength(30)
#MinLength(4)
public String password;
#ManyToOne(fetch = FetchType.EAGER)
#Column(nullable = false)
public Role role;
public static Finder<Long, User> find = new Finder<Long, User>(Long.class, User.class);
public User() {
}
public User(String username, String password, Role role) {
this.username = username;
this.password = password;
this.role = role;
}
#Override
public String toString() {
return "[Tying to login : ] [" + username + " - " + password + "]";
}
}
In my controller, I want to get a user's role instance, so here's what I did:
public static Result modules(Long id) {
User user = User.find.byId(id);
if ("Super User".equalsIgnoreCase(user.role.name)) {
return ok();
} else {
return forbidden();
}
}
The problem is, user.role.name is null, but user.role.id is correct here, why EBean doesn't help me to fetch role for users ?
I have experienced this problem on different occasions. You could do the following:
First, try to replace your public fields with private ones and the add the appropriate getters and setters (this is a good pattern when using Java anyway).
Second, you can write a little helper for finding/fetching the needed information. So let's say you need to get the user by Id and the do this string check. Then in your User class you can write a method like this:
public static User findById(Long id) {
return Ebean.find(User.class)
.fetch("role")
.where()
.eq("id", id)
.findUnique();
}
After that, just use the method:
public static Result modules(Long id) {
User user = User.findById(id);
if ("Super User".equalsIgnoreCase(user.getRole().getName())) {
return ok();
} else {
return forbidden();
}
}
This is my annotation class and i want userId and groupId column both as primary key.
I have found more questions (Question) about this, but didn't found relevant answer.
I have less reputation, so I am not able to comment on posts, So I am putting my question here.
This is my code..
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.annotations.NaturalId;
#Entity
#Table(name="user_group")
public class user_group {
#Column(name="serviceProvider")
private String serviceProvider;
#Column(name="enterpriseId")
private String enterpriseId;
#Column(name="department")
private String department;
#Column(name="trunkGroupName")
private String trunkGroupName;
#Id
#Column(name="userId")
private String userId;
#Column(name="groupId")
private String group;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getGroup() {
return group;
}
public void setGroup(String group) {
this.group = group;
}
public String getServiceProvider() {
return serviceProvider;
}
public void setServiceProvider(String serviceProvider) {
this.serviceProvider = serviceProvider;
}
public String getEnterpriseId() {
return enterpriseId;
}
public void setEnterpriseId(String enterpriseId) {
this.enterpriseId = enterpriseId;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public String getTrunkGroupName() {
return trunkGroupName;
}
public void setTrunkGroupName(String trunkGroupName) {
this.trunkGroupName = trunkGroupName;
}
}
You should create a new #Embeddable class containing the PK fields:
#Embeddable
public class user_groupId implements Serializable {
#Column(name="userId")
private String userId;
#Column(name="groupId")
private String group;
}
And use it in the #Entity as an #EmbeddedId:
#Entity
public class user_group {
#EmbeddedId
user_groupId id;
...
}
You could also use the #IdClass annotation to that effect.
This excellent answer by Pascal Thivent elaborates on the details. You can also take a look at this other answer I posted to a almost identical question some time ago.
As a side note, if you've got control over the DB structure, you might also consider avoiding composite keys. There are some reasons to do so.
you can create a composite primary key in hibernate using #UniqueConstraint annotation.
#Table(name="user_group",uniqueConstraints=#UniqueConstraint(columnNames= {"userId","groupId"}))
public class user_group
{
#Column(name="userId")
private String userId;
#Column(name="groupId")
private String group;
}
above method is not feasible if we use spring because for creating composite primary key we have to create a class is not a good thing.
in hibernate and spring you only have to create POJO classes which are available as an entity on your system.
I suppose it is not standard way of doing that so any tips will be helpful, here is my code:
#RequestMapping("/register")
public String register(Map<String, Object> map, #ModelAttribute("user") MyUser user) {
if(user.getLogin() == ""){
map.put("user", new MyUser());
}
else{
map.put("user", user);
map.put("result", userService.addMyUser(user));
}
return "register";
}
what cause following error:
org.hibernate.AssertionFailure: null id in org.mypackage.MyUser entry
(don't flush the Session after an exception occurs)
Here is MyUser class:
#Entity
#Table(name="MyUser")
public class MyUser{
#Id
#Column(name="idMyUser")
#GeneratedValue
private Integer id;
#Column(name="login")
private String login;
#Column(name="password")
private String password;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Try changing the strategy and/or generator for the #GeneratedValue, see here and here for details (for example, you could try #GeneratedValue(strategy = GenerationType.IDENTITY). You could also check if your database table is set to generate the primary key values. The exception seems to indicate that the primary key -field is left unset by the current strategy and/or generator.