I use in my project Hibernate. And have error when the server start:
org.hibernate.PersistentObjectException: detached entity passed to persist
Following "autorization" code:
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "login", unique = true, updatable = false)
private String name;
//Important to Hibernate!
#SuppressWarnings("MySQLConfig")
public UsersDataSet() {
}
#SuppressWarnings("MySQLConfig")
public UsersDataSet(long id, String name) {
this.setId(id);
this.setName(name);
}
public UsersDataSet(String name) {
this.setId(-1);
this.setName(name);
}
Please, explain me, where am I wrong?
I'm suspecting that you are trying to do session.persist(userData) rather than doing session.saveOrUpdate(..) (will update the data on the detached object) or session.save(..) (Will create a new row). Please try any of these last two depending on what you want.
Next line to create id automatically. That's why you have an exception.
#GeneratedValue(strategy = GenerationType.IDENTITY)
JPA strategy generated id automatically when saving essence. But the constructor has written
that is necessary to do manually. That's why JPA thinking detached from persistence context.
For fix it you can change direction id or GeneratedValue(strategy = …)
public UsersDataSet(long id, String name) {
// this.setId(id);
this.setName(name);
}
Related
I am trying to update the parent entity and child entity (comes with parent) using JPA. I tried many ways but I couldn't do it. It gives me the following error.
javax.persistence.EntityExistsException: A different object with the
same identifier value was already associated with the session Contact(6)
Following is the entity,
Parent -> User.java
#Entity
public class User extends Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_generator")
#SequenceGenerator(name = "seq_generator", sequenceName = "seq_users", initialValue = 1, allocationSize = 1)
private Long id;
private String firstName;
private String lastName;
private String email;
private String password;
private String street;
private String city;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "userContact")
private Contact contact;
}
Child -> Contact.java
#Entity
public class Contact extends Serializable {
private static final long serialVersionUID = 1L;
#Getter(value = AccessLevel.NONE)
#Setter(value = AccessLevel.NONE)
#Id
#Column(insertable = false, updatable = false)
private Long id;
#MapsId
#OneToOne
#JoinColumn(name = "id", nullable = false)
private User userContact;
private String phone;
}
In my Service.java I am updating like this,
#Override
#Transactional
public User update(Long id, User user) {
Optional<User> curUser = userRepo.findById(id);
user.setId(curUser.get().getId());
user.setStreet(curUser.get().getStreet());
user.setCity(curUser.get().getCity());
Contact contact = user.getContact();
contact.setUserContact(user);
user.setContact(contact);
return userRepo.save(user);
}
And the update JSON payload is like this,
{
"firstName": "FirstName",
"lastName": "LastName",
"email": "Email",
"password": "Password",
"contact": {
"phone": "0123456789"
}
}
So why I can't update this at once? The insert is working fine but this update is not working. What I am doing wrong here? I really appreciate it if anybody can help me. Thanks in advance.
Here is the new way I tried,
#Override
#Transactional
public User update(Long id, User user) {
Optional<User> curUser = userRepo.findById(id);
curUser.setFirstName(user.getFirstName());
curUser.setlastName(user.getLastName());
curUser.setEmail(user.getEmail());
curUser.setPassword(user.getPassword());
Contact contact = user.getContact();
contact.setUserContact(curUser);
curUser.setContact(contact);
return userRepo.save(curUser);
}
Because you have the same user (i.e. same id) represented twice in the persistence context.
This line loads the user curUser in the persistence context.
Optional<User> curUser = userRepo.findById(id);
This line makes user the same user by id, but it is a different instant.
user.setId(curUser.get().getId());
And finally this line tries to add user to the persistence context.
userRepo.save(user);
This would result with the two instances being attached to the persistence context while representing the same user with the same id which would result in an inconsistent state since JPA wouldn't know which version to persist. Therefore this is forbidden and throws the exception you are seeing.
In your case changing
Optional<User> curUser = userRepo.findById(id);
user.setId(curUser.get().getId());
to
user.setId(id);
should do the trick.
Regarding your comments:
The same problem exists for referenced objects. You'll have to make sure that you: either load an entity and change it's attribute, or do NOT load the entity, not directly nor indirectly via a reference and save a detached entity, constructed from your request.
If you load an entity and then try to save a detached instance of it, you will get this kind of error.
I have a hibernate entity Car
#Entity
#Table(name = "car")
public class Car extends AbstractEntityBean implements java.io.Serializable {
private Integer carId;
private Integer name;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "carId", unique = true, nullable = false)
public Integer getCarId() {
return this.carId;
}
public void setCarId(Integer carId) {
this.carId = carId;
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
Then I try to search a car by example:
crit.add(example);
In "example" I set carName adn search works great, as expected.
But when I set carId in "example", carId is being ignored in search criteria and query returns all cars.
I did some debuggin and I see in hibernate-core-3.6.10.Final-sources.jar!\org\hibernate\tuple\entity\EntityMetamodel.java there is a property
private final String[] propertyNames;
I can see "name" on propertyNames list, but not cardId.
When I add annotation #Id to name instead of carId, carId shows up on propertyNames list and I can search by example where carId is set.
Question: How can I search by example using carId when #Id annotation is set at carId?
Thanks
As I remember from QBE with Hibernate criteria, this is the way it is designed. So short question, you can't query by the id using QBE. And it also doesn't make sense. When querying by id, you can only get one result.
As a side note: Hibernate 3.6 is quite old. Probably way out-of-date!
I am using Dropwizard-1.1.2 with hibernate-5.2.8. I implemented one-to-many relationship like this:
Parent Table:
#TypeDefs( {#TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)})
#Table(name="parent_table")
#Entity
public class ParentTable {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
#Column(name = "parent_id", updatable = false, nullable = false)
private UUID id;
#JoinColumn(name="parent_id")
#OneToMany(targetEntity = NotificationModel.class, cascade = {CascadeType.ALL}, fetch = FetchType.LAZY)
private List<NotificationModel> notifications = new ArrayList<>();
public UUID getId() {
return id;
}
public List<NotificationModel> getNotifications() {
return notifications;
}
public void setNotifications(List<NotificationModel> notifications) {
this.notifications = notifications;
}
}
Notification Table
#Table(name="notifications")
#Entity
public class ReminderNotificationModel {
#Id
#GeneratedValue
private Integer id;
#Column(name = "notification_id")
#Type(type="pg-uuid")
private UUID notificationId;
private String message;
#Column(name = "notification_status")
private String notificationStatus;
#Column(name = "scheduled_at")
private DateTime scheduledAt;
// getters and constructors
}
Now in my DAO no matter if I try native-query, criteria query or get by id on parent table, it always gives me all the notifications as well. Should the notifications be fetched lazily?
DAO
public class ReminderDao extends AbstractDAO<ReminderModel> {
#Inject
public ReminderDao(SessionFactory sessionFactory) {
super(sessionFactory);
}
public ReminderModel getById(UUID id) {
ReminderModel m = get(id); // m has notifications as well
return m;
}
}
I was reading hibernate's documentation which says Lazy is a hint for basic types. However collection is not a basic type. So lazy should have been honored. What am I missing?
LAZY is merely a hint that the value be fetched when the attribute is
accessed. Hibernate ignores this setting for basic types unless you
are using bytecode enhancement
"Loading the notifications of a parent lazily" is not the same thing as "not loading notifications of a parent and pretend the parent has no notification at all".
Loading lazily means that the notifications are loaded only when the code reads them from the list, for example when printing them with the debugger, or when serializing them to JSON, or when getting the size of the list.
If you really want to test that lazy loading works correctly, then load a parent, end the transaction and close the entity manager, and check that getting the size of the list throws an exception. Or load a parent, then detach it, then check that getting the size of the list throws an exception. Or load the parent, and check that Hibernate.isInitialized(parent.getNotifications()) is false.
I want to assign a category to a recipe. If I assign a second category with the same name to the recipe it does another insert to the database that aborts (Abort due to constraint violation (UNIQUE constraint failed: category.name) - this is actually fine). I want to reuse this entry and attach it to the recipe. Is there a JPA way to do this "automatically" or do I have to handle this? Should I search for a category with the same name in the setCategory method and use this one if present? Is there a Pattern?
#Entity
public class Recipe {
private Integer id;
private Category category;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "category_id", referencedColumnName = "id")
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}
#Entity
public class Category {
private Integer id;
private String name;
private List<Recipe> recipes;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy = "category", cascade = {CascadeType.ALL})
public List<Recipe> getRecipes() {
return recipes;
}
public void setRecipes(List<Recipe> recipes) {
this.recipes = recipes;
}
}
Example:
Category category = new Category();
category.setName("Test Category");
cookbookDAO.add(cookbook);
Recipe recipe = new Recipe();
recipe.setTitle("Test Recipe");
recipe.setCategory( category );
recipeDAO.add(recipe);
Executing this twice results in the UNIQUE constraint failed: category.name. This is fine since I don't want multiple categories with the same name. The database enforced this but I'm looking for the soltuion to enforce this on the java language level too.
The DDL:
CREATE TABLE "recipe"
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_id INTEGER,
FOREIGN KEY (category_id) REFERENCES category(id)
);
CREATE TABLE "category"
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR,
UNIQUE(`name`) ON CONFLICT ABORT
);
Hello the behavior you are describing is a result of the mapping
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "category_id", referencedColumnName = "id")
public Category getCategory() {
return category;
}
If we translate this mapping is simple language. You are saying:
Everytime I attempt to save a Recipe the corresponding Category should
be persisted as well.
The problem here comes from the fact that you already have an existing Category so the #Cascade.PERSIST here is not appropriate.
The semantics here is the opposite . A Recipie creates a Category only if a Category does not exist yet. This mean that the creation of the Category is more of an exception than a general rule.
This mean that you have two options here.
Option 1 is to remove Cascade.PERSIST.
Option 2 is to replace it with Cascade.MERGE.
Option 3 is to go the other way. Instead of annotating the #ManyToOne relationship in Recipe to Category with Cascade.PERSIST to annotate the #OneToMany relationship from the Category to Recipe.
The advantage of such approach is very simple. While you not always want to create a new category when adding a new recipe. It is 100% all the time you want to add a new Category you also want to add all the attached Recipies.
Also I will recommend you to favor Bidirectional relationships over unidirectional ones and read this article about merge and persist JPA EntityManager: Why use persist() over merge()?
The problem is you are creating a new category with the following statement:
Category category = new Category();
Because this instance of the Category entity is new and accordingly does not have a persistent identity the persistence provider will try to create a new record in the database. As the name field of the category table is unique you get constraint violation exception from the database.
The solution is first fetching the category and assign it to recipe. So what you have to do is the following:
String queryString = "SELECT c FROM Category c WHERE c.name = :catName";
TypedQuery<Category> query = em.createQuery(queryString, Category.class);
em.setParameter("catName", "Test Category");
Category category = query.getSingleResult();
This fetched instance is a managed entity and the persistence provider will not try to save. Then assign this instance to the recipe as you already did:
recipe.setCategory( category );
In this case the cascading will just ignore saving the category instance when recipe is saved. This is stated in the JPA 2.0 specification in section 3.2.2 Persisting an Entity Instance as follows:
If X is a preexisting managed entity, it is ignored by the persist operation.
I've been working on a project which is now in production mode. Now I've been told to remove the mappings completely from the .hbm.xml files so that I need to handle every relationships manually in the program code. It's really a big problem coz the every DB operation which I've written is in Hibernate Criteria.
Just consider the following Criteria
Criteria criteria = getSession().createCriteria(Table1.class,"table1");
criteria.createAlias("table1.table2", "table2")
.createAlias("table1.table3", "table3")
.createAlias("table3.table4", "table4")
.createAlias("table3.table5", "table5")
.setProjection(Projections.projectionList()
.add(Projections.property("id"),"id")
.add(Projections.property("c1"),"c1")
.add(Projections.property("c2"),"c2")
.add(Projections.property("c3"),"c3")
.add(Projections.property("table2.c1"),"table2.c1")
.add(Projections.property("table2.c2"),"table2.c2")
.add(Projections.property("table3.c1"),"table3.c1")
.add(Projections.property("table5.c1"),"table3.table5.c1"))
.add(Restrictions.eq("table4.c1", Constants.STATUS_ENABLED))
.setResultTransformer(new AliasToBeanNestedResultTransformer(Table1.class));
return criteria.list();
This is the criteria which is written when all the relationships are present in .hbm.xml files. Now you can understand what will be problem I'm going to face when removing the mapping from .hbm.xml files. TBH, I've to rework entire DAO classes by removing Criteria and replacing it with HQL. Also I'll not be able to fetch the result directly as object by using HQL.
Is it possible to only make small changes to the criteria (like defining the join between tables in the criteria itself) so that I'll get the same output even after removing the mappings from .hbm.xml files..?
Yes you can use the Java Persistence Annotations in the Entity classes and will work the same way the .hbm.xml classes do.
Take this for example
#Entity
public class Employee {
#SequenceGenerator(name="EMPLOYEE_SEQ", sequenceName="EMPLOYEE_SEQ", initialValue=1, allocationSize=1)
#Id #GeneratedValue(strategy=GenerationType.AUTO, generator="EMPLOYEE_SEQ")
private int id;
#Column(nullable = false, length = 50)
private String name;
#ManyToOne(targetEntity = Country.class, optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "loanID", updatable = false, insertable = false)
private Loan loan;
#Column(name = "loanID", updatable = true, insertable = true)
private Integer loanID;
public int getId() {
return id;
}
public void setCompanyID(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getLoanD() {
return loanID;
}
public void getLoanD(Integer loanID) {
this.loanID = loanID;
}
}
And then you just use the criterias like you used to.