JPA: On delete cascade #OneToMany relationship EclipseLink - java

I have these two entity classes:
#Entity
#Table(name = "USER")
#XmlRootElement
#CascadeOnDelete
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 15)
#Column(name = "USERNAME")
private String username;
//...
#OneToMany(mappedBy = "username", cascade = CascadeType.ALL, orphanRemoval=true)
#CascadeOnDelete
private Collection<Post> postCollection;
//...
}
And:
#Entity
#Table(name = "POST")
#XmlRootElement
public class Post implements Serializable {
// ...
#JoinColumn(name = "USERNAME", referencedColumnName = "USERNAME")
#ManyToOne
private User username;
//...
}
I have some posts attached to the same user. If I delete one of them it works right. But when I try to delete that user from the DB (using the EntityManager) I get java.sql.SQLIntegrityConstraintViolationException foreign key violation restriction.
Is there a way to delete all that posts when the user (their foreign key) is deleted? Simply an ON DELETE CASCADE SQL statement.
I'm using Derby (Java DB) and EclipseLink. Adding those #CascadeOnDeleteannotations from JPA Extensions for EclipseLink is the last thing I've tried, with no success at all.
EDIT:
This is the code I use for removing an user (it´s a REST API)
#DELETE
#Path("{id}")
public Response remove(#PathParam("id") String id) {
User u;
if((u = super.find(id)) == null)
return Response.status(Response.Status.NOT_FOUND).build();
super.remove(u);
return Response.status(Response.Status.NO_CONTENT).build();
}
And, in the superclass:
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}

Can we try adding the ON DELETE SET NULL clause to the foreign key constraint in the POST table. This would help to set the corresponding records in the child table to NULL when the data in the parent table is deleted. So if a username value is deleted from the user table, the corresponding records in the POST table that use this username will have the username set to NULL.

Related

Duplicate entry when using one to one relationship with shared primary key in JPA

I followed the example of Modeling With a Shared Primary Key as below:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
//...
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address address;
//... getters and setters
}
#Entity
#Table(name = "address")
public class Address {
#Id
#Column(name = "user_id")
private Long id;
//...
#OneToOne
#MapsId
#JoinColumn(name = "user_id")
private User user;
//... getters and setters
}
However, if there are already a record with id 123456 in address table, then I tried to update the record like below:
Address po = new Address();
po.setId(123456L);
po.setCountry("TW");
AddressRepository.save(po);
Duplicate entry '123456' for key Exception will occur. Why JPA will insert a new record instead of merging it? How to solve this problem?
I know the reason finally. It is because the entity has version field and the version field in the new entity is null.
We need to dig into the source of of save() method in JPA.
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Then, if we don't override the isNew(), it will use the default isNew() of JpaMetamodelEntityInformation.
#Override
public boolean isNew(T entity) {
if (!versionAttribute.isPresent()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
Here, we can see that if version is present and the version is different from the existing record in the database, the entity will be a new entity and JPA will execute the insert action. Then, it will occur the error of duplicate entry.

integrity constraint violation: unique constraint or index violation on Foreign key HSQL

I'm testing the underlying model of a HSQL database using Hibernate/Spring Boot and I've run into an issue I cannot find a solution to.
This is my simple test, I'm trying to create a shoebox entity and save it to the database with a User object set as the FK for Owner:
#TestConfiguration
static class ShoeboxServiceTestContextConfiguration {
#Bean
public ShoeboxService shoeboxService() {
return new ShoeboxService();
}
#Bean
public UserService userService() {
return new UserService();
}
}
#Autowired
UserService users;
#Autowired
ShoeboxService shoeboxes;
#Test
public void testSave()
{
System.out.println("save");
int userId = 1;
User user = new User(userId, "Foo", "hello#world.com");
user = users.save(user);
Shoebox sb = new Shoebox(user, "Name", "Context", "Comment", false);
UUID sbId = shoeboxes.save(sb).getId();
sb = shoeboxes.findOne(sbId);
assertNotNull(sb);
assertEquals(sb.getName(), "Name");
assertEquals(sb.getContext(), "Context");
assertEquals(sb.getComment(), "Comment");
assertEquals(sb.isShare(), false);
shoeboxes.deleteById(sbId);
users.deleteById(userId);
}
However when it gets it throws a
integrity constraint violation: unique constraint or index violation; SYS_PK_10126 table: USER
exception when it tries to save the Shoebox to the DB. It successfully persist the User, and it succeeds in persisting the Shoebox object when there is no Owner FK attached to it, but crashes when the FK is supplied.
Here is my User POJO:
#Entity
#Table(name="User")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User implements Serializable
{
#Id
#Column(name = "ID")
private long ID;
#Column(name = "Name")
private String name;
#Column(name = "Email")
private String email;
#OneToOne(fetch = FetchType.LAZY)
private Shoebox currentlySelectedBox;
#OneToMany(fetch = FetchType.LAZY)
#JsonManagedReference(value="shoebox_owner")
private List<Shoebox> shoeboxes;
// Contructors, Getters/Setters etc.
}
And my Shoebox POJO:
#Entity
#Table(name="Shoebox")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Shoebox implements Serializable
{
#Id
#Column(name="ID")
UUID ID;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="OwnerID")
#JsonBackReference(value="shoebox_owner")
User owner;
#Column(name="Name")
String name;
#Column(name="Context")
String context;
#Column(name="Comment")
String comment;
#Column(name="Shared")
boolean share;
#Column(name="CreationDate")
LocalDateTime creationDate;
// Contructors, Getters/Setters etc.
}
Here is the HSQL creation script for the DB:
CREATE MEMORY TABLE PUBLIC.SHOEBOX(ID BINARY(255) NOT NULL PRIMARY KEY,COMMENT VARCHAR(255),CONTEXT VARCHAR(255),CREATIONDATE TIMESTAMP,NAME VARCHAR(255),SHARED BOOLEAN,OWNERID BIGINT)
CREATE MEMORY TABLE PUBLIC.USER(ID BIGINT NOT NULL PRIMARY KEY,EMAIL VARCHAR(255),NAME VARCHAR(255),CURRENTLYSELECTEDBOX_ID BINARY(255),CONSTRAINT FK3T924ODM2BIK5543K0E3UEGP FOREIGN KEY(CURRENTLYSELECTEDBOX_ID) REFERENCES PUBLIC.SHOEBOX(ID))
CREATE MEMORY TABLE PUBLIC.USER_SHOEBOX(USER_ID BIGINT NOT NULL,SHOEBOXES_ID BINARY(255) NOT NULL,CONSTRAINT FK5W8WMFC5E9RMEK7VC4N76MQVQ FOREIGN KEY(SHOEBOXES_ID) REFERENCES PUBLIC.SHOEBOX(ID),CONSTRAINT FKIR9SOKRCOQ33LCQTNR0LDXO93 FOREIGN KEY(USER_ID) REFERENCES PUBLIC.SHOEBOXUSER(ID),CONSTRAINT UK_508XA86IDIHP04FQD3D6GF8D7 UNIQUE(SHOEBOXES_ID))
ALTER TABLE PUBLIC.SHOEBOX ADD CONSTRAINT FK3J9RQBYW5VQ0IRF3FWYPG7LAB FOREIGN KEY(OWNERID) REFERENCES PUBLIC.USER(ID)
Why is the exception being triggered? Is there something wrong with my annotations and PK/FK relationships between the objects?
Many Thanks.
The issue is
#ManyToOne(cascade = CascadeType.ALL)
With CascadeType.ALL, any operations will extend to the other entities. So in this case the save method is cascading on the shoebox's user attempting to save it again. Since you are using a static id of 1, it is causing a key constraint.

How to remove relationship between many to many table with JPA and Hibernate

I have two tables: Recipe table and Account table. The Recipe table stores a number of recipes. The account table stores a number of user account. A user can be associated with 0 or more recipe. When a user likes a recipe, this user is associated to the recipe. To record this association, I created a table called LikedRecipe table.
These are the columns of each table:
Recipe: id, name. Id is the primary key.
Account: email, password. Email is the primary key.
LikedRecipe: id, name, email. id is the primary key.
#Entity
#Table(name = "Recipe")
public class Recipe {
private Set<Account> account;
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "LikedRecipe", joinColumns = #JoinColumn(name = "recipeId"), inverseJoinColumns = #JoinColumn(name = "email"))
public Set<Account> getAccount() {
return account;
}
public void setAccount(Set<Account> account) {
this.account = account;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
#Entity
#Table(name = "Account")
public class Account implements Serializable {
private Set<Recipe> likedRecipes = new HashSet<Recipe>();
private String email;
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinTable(name = "LikedRecipe", joinColumns = #JoinColumn(name = "email"), inverseJoinColumns = #JoinColumn(name = "recipeId"))
public Set<Recipe> getLikedRecipes() {
return likedRecipes;
}
public void setLikedRecipes(Set<Recipe> likedRecipes) {
this.likedRecipes = likedRecipes;
}
#Column(name = "email")
#Id
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
I wrote a method to remove the association between an account and a recipe:
public void unlikeARecipe(String email, Long recipeId){
Query query = entityManager
.createQuery("delete from LikedRecipe where recipeId = :recipeId and email = :email");
query.setParameter("recipeId", recipeId);
query.setParameter("email", email);
query.executeUpdate();
}
This method did not delete records from LikedRecipe table, until I added this line of code at the end of the method:
entityManager.clear();
According to JPA API documentation the clear method clears the persistence context, causing all managed entities to become detached.
My question is what does detach means ? And, how does detaching objects made the above method deletes records from LikedRecipe table? Am I using the clear method in the right manner ?
Thank you.
Detached entity is an entity not currently managed by a persistence context but whose id is present in database.
I think you don't get the LikedRecipe entity deleted because you still have it referenced from other persistent entities (Account and Recipe). Indeed it works when you clear the persistence context, detaching all entities that are "keeping alive" the LikedRecipe you wanted to delete.
If you want to keep the many-to-many relationships, you have to clear them as well (i.e. removing the object from Account's and Recipe's collections) when you're about to deleting the LikedRecipe entity.
Shouldn´t be better to make design something like:
- Recipe(id,name)
- User(email, ...)
- LikedRecipe(userEmail, recipeId)?
And don´t make relationship in recipe for users, it´s (in my humble opinion) useless relationship.
To this case it´s enough to make OneToMany relationship from User (/Account) to Recipe and none relation in Recipe for Users.

Hibernate OneToOne relationship

I have two persistence entity: User and UserDetail. They have one-to-one relationship. I use hibernate annotations. But I am getting in my database several objects of user information for one same user. Apparently my knowledge of Hibernate annotations are not so good to solve this problem.
User class:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private UserDetail userDetail;
// setters and getters
}
UserDetail class:
#Entity
#Table(name = "USER_DETAIL")
public class UserDetail {
#OneToOne
#JoinColumn(name = "USER_ID")
private User user;
// other fields
}
I use this in my code as follows:
UserDetail userDetail = new UserDetail();
userDetail.setInfo(info);
userDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(userDetail);
And everything works properly. Here's what my table USER_DETAIL:
But when I try to change user information, I get an incorrect behavior. I get following table after I again set user information:
UserDetail newUserDetail = new UserDetail();
newUserDetail.setInfo(newInfo);
newUserDetail.setUser(seventhUser);
hibernateTemplate.saveOrUpdate(newUserDetail);
Why the same two objects of information correspond to one user?? I have One-To-One relationship. How can I avoid this? What am I doing wrong?
If you want to modify an existing UserDetail, then you must set its ID, or get it from the session and modify it. Else, Hibernate thinks it's a new one that must be saved, since it doesn't have any ID.
UserDetail existingUserDetail = session.get(UserDetail.class, theUserDetailId);
existingUserDetail.setInfo(newInfo);
To make sure you don't save two UserDetail instances for the same user, you should add a unique constraint on the USER_ID column of the UserDetail database table.

OneToOne between two tables with shared primary key

I'm trying to set up the following tables using JPA/Hibernate:
User:
userid - PK
name
Validation:
userid - PK, FK(user)
code
There may be many users and every user may have max one validation code or none.
Here's my classes:
public class User
{
#Id
#Column(name = "userid")
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long userId;
#Column(name = "name", length = 50, unique = true, nullable = false)
protected String name;
...
}
public class Validation
{
#Id
#Column(name = "userid")
protected Long userId;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn(name = "userid", referencedColumnName = "userid")
protected User user;
#Column(name = "code", length = 10, unique = true, nullable = false)
protected String code;
...
public void setUser(User user)
{
this.user = user;
this.userId = user.getUserId();
}
...
}
I create a user and then try to add a validation code using the following code:
public void addValidationCode(Long userId)
{
EntityManager em = createEntityManager();
EntityTransaction tx = em.getTransaction();
try
{
tx.begin();
// Fetch the user
User user = retrieveUserByID(userId);
Validation validation = new Validation();
validation.setUser(user);
em.persist(validation);
tx.commit();
}
...
}
When I try to run it I get a org.hibernate.PersistentObjectException: detached entity passed to persist: User
I have also tried to use the following code in my Validation class:
public void setUserId(Long userId)
{
this.userId = userId;
}
and when I create a validation code I simply do:
Validation validation = new Validation();
validation.setUserId(userId);
em.persist(validation);
tx.commit();
But then since User is null I get org.hibernate.PropertyValueException: not-null property references a null or transient value: User.code
Would appreciate any help regarding how to best solve this issue!
I have been able to solve this problem of "OneToOne between two tables with shared primary key" in pure JPA 2.0 way(Thanks to many existing threads on SOF). In fact there are two ways in JPA to handle this. I have used eclipselink as JPA provider and MySql as database. To highlight once again no proprietary eclipselink classes have been used here.
First approach is to use AUTO generation type strategy on the Parent Entity's Identifier field.
Parent Entity must contain the Child Entity Type member in OneToOne relationship(cascade type PERSIST and mappedBy = Parent Entity Type member of Child Entity)
#Entity
#Table(name = "USER_LOGIN")
public class UserLogin implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="USER_ID")
private Integer userId;
#OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
private UserDetail userDetail;
// getters & setters
}
Child Entity must not contain an identifier field. It must contain a member of Parent Entity Type with Id, OneToOne and JoinColumn annotations. JoinColumn must specify the ID field name of the DB table.
#Entity
#Table(name = "USER_DETAIL")
public class UserDetail implements Serializable {
#Id
#OneToOne
#JoinColumn(name="USER_ID")
private UserLogin userLogin;
// getters & setters
}
Above approach internally uses a default DB table named SEQUENCE for assigning the values to the identifier field. If not already present, This table needs to be created as below.
DROP TABLE TEST.SEQUENCE ;
CREATE TABLE TEST.SEQUENCE (SEQ_NAME VARCHAR(50), SEQ_COUNT DECIMAL(15));
INSERT INTO TEST.SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0);
Second approach is to use customized TABLE generation type strategy and TableGenerator annotation on the Parent Entity's Identifier field.
Except above change in identifier field everything else remains unchanged in Parent Entity.
#Entity
#Table(name = "USER_LOGIN")
public class UserLogin implements Serializable {
#Id
#TableGenerator(name="tablegenerator", table = "APP_SEQ_STORE", pkColumnName = "APP_SEQ_NAME", pkColumnValue = "USER_LOGIN.USER_ID", valueColumnName = "APP_SEQ_VALUE", initialValue = 1, allocationSize = 1 )
#GeneratedValue(strategy = GenerationType.TABLE, generator = "tablegenerator")
#Column(name="USER_ID")
private Integer userId;
#OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
private UserDetail userDetail;
// getters & setters
}
There is no change in Child Entity. It remains same as in the first approach.
This table generator approach internally uses a DB table APP_SEQ_STORE for assigning the values to the identifier field. This table needs to be created as below.
DROP TABLE TEST.APP_SEQ_STORE;
CREATE TABLE TEST.APP_SEQ_STORE
(
APP_SEQ_NAME VARCHAR(255) NOT NULL,
APP_SEQ_VALUE BIGINT NOT NULL,
PRIMARY KEY(APP_SEQ_NAME)
);
INSERT INTO TEST.APP_SEQ_STORE VALUES ('USER_LOGIN.USER_ID', 0);
If you use Hibernate you can also use
public class Validation {
private Long validationId;
private User user;
#Id
#GeneratedValue(generator="SharedPrimaryKeyGenerator")
#GenericGenerator(name="SharedPrimaryKeyGenerator",strategy="foreign",parameters = #Parameter(name="property", value="user"))
#Column(name = "VALIDATION_ID", unique = true, nullable = false)
public Long getValidationId(){
return validationId;
}
#OneToOne
#PrimaryKeyJoinColumn
public User getUser() {
return user;
}
}
Hibernate will make sure that the ID of Validation will be the same as the ID of the User entity set.
Are you using JPA or JPA 2.0 ?
If Validation PK is a FK to User, then you do not need the Long userId attribute in validation class, but instead do the #Id annotation alone. It would be:
Public class Validation
{
#Id
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn(name = "userid", referencedColumnName = "userid")
protected User user;
#Column(name = "code", length = 10, unique = true, nullable = false)
protected String code;
...
public void setUser(User user)
{
this.user = user;
this.userId = user.getUserId();
}
...
}
Try with it and tell us your results.
You need to set both userId and user.
If you set just the user, then the id for Validation is 0 and is deemed detached. If you set just the userId, then you need to make the user property nullable, which doesn't make sense here.
To be safe, you can probably set them both in one method call:
#Transient
public void setUserAndId(User user){
this.userId = user.getId();
this.user = user;
}
I marked the method #Transient so that Hibernate will ignore it. Also, so you can still have setUser and setUserId work as expected with out any "side effects."

Categories

Resources