How to delete assignment from #ManyToMany relation? - java

I have a problem with deleting (by unassign only) roles which are assigned to user through association table. Only assignment should be deleted.
I need to have a User entity with relation ManyToMany to Rolle entity but this relation (assignment) needs to have additional information like expiriation_date (because assignment can expire). So, I cannot use automatically created and managed association table by hibernate (#JoinTable - which was working really great when I didn't need an additional column) but now I need to extend the association with an extra column and that's why instead of that #JoinTable I need to create such a association table manualy. Below my model relation which is working when I am adding some roles to user, but not working when I try to delete some role.
USER:
#Entity
#Table(name = "USER")
public class User {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<UserRole> userRoleAssignments = new LinkedHashSet<>();
//getters, setters
public List<BrugersystemrolleDB> getRoles() {
return this.userRoleAssignments.stream().
map(roleAssignment -> roleAssignment.getRole()).
collect(Collectors.toList());
}
public void setRoles(List<Role> roles) {
this.userRoleAssignments = roles.stream().
map(role -> useExistingOrNewRoleAssignment(role)).
collect(Collectors.toSet()));
}
private UserRole useExistingOrNewRoleAssignment(Role role) {
Optional<Role> roleAssignmentOpt = getRoleAssignment(role);
if (roleAssignmentOpt.isPresent()) {
return roleAssignmentOpt.get();
} else {
return new UserRole(role, this);
}
}
private Optional<UserRole> getRoleAssignment(Role role) {
return roleAssignments.stream().
filter(roleAssignment ->
roleAssignment.getRole().getId() == role.getId()).findFirst();
}
}
ROLE:
#Entity
#Table(name = "ROLE")
public class Role {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "role", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<UserRole> userRoleAssignments = new LinkedHashSet<>();
//getters, setters
}
USER_ROLE:
#Entity
#Table(name = "USER_ROLE")
public class UserRole {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "ROLE_ID")
private Role role;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "EXPIRE_DATE", nullable = true)
private Date expireDate;
public UserRole (Role role, User user) {
this.role = role;
this.user = user;
}
//getters, setters
}
Usage:
#Stateless
public class UserLogic {
#EJB private UserDao userDao;
#EJB private UserRoleDao userRoledao;
//... just some generic solution for update requests
public User updateUser(User user, UserVo, userVo) {
user.setName(userVo.getName());
assignRollesToUser(user, userVo.getRoles());
return userDao.update(user); // <-- is causing an error when some role is deleted!
}
// this method is responsible for roles management and is causing a problem during update user list of roles, but only when some of them needs to be deleted.
public void assignRollesToUser(User user, List<Integer> roles) {
// 1 step: remove all existing assignments:
user.getRoleAssignments().stream().
forEach(roleAssignment -> {
// 1) solution with userRoleDao is causing a problem with deleted/detached object passed to merge:
userRoleDao.delete(roleAssignment);
// 2) solution with removing role from list gives me an error saying: NULL not allowed for column 'USER_ID' which is true, but why hibernate is not deleting this entry and wants to to set null for user_id which breaks a constraint?
// user.getRoleAssignments().remove(userAssignment);
});
// 2 step: assign new set of roles
List<Role> newRolesToAssign = getRolesFromDB(roles);
user.setRoles(newRolesToAssign )
}
private List<Role> getRolesFromDB(List<Integer> rolles) {
return rolles.stream().
map(userRoleDao::read).
collect(Collectors.toList());
}
}
I am able to add new roles to user but cannot delete any role and when try to delete then I am receiving
deleted instance passed to merge
or
detached instance passed to merge
It depends on way of deleting. I tried to delete roles via dao.delete() or via removing from collection only without removing by dao. Both solutions didn't work. Can you give me please some idea what am I doing wrong? Am I something missing?
EDIT:
Below the error which appears now after change in forEach in UserLogic to:
forEach(roleAssignment -> {
user.getRoleAssignments().remove(userAssignment);
roleAssignment.setUser(null);
roleAssignment.setRole(null);
});
Error:
[Server:server-one] Caused by: java.util.ConcurrentModificationException
[Server:server-one] at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429) [rt.jar:1.8.0_45-internal]
[Server:server-one] at java.util.HashMap$KeyIterator.next(HashMap.java:1453) [rt.jar:1.8.0_45-internal]
[Server:server-one] at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPer
sistentCollection.java:789) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
[Server:server-one] at java.util.Iterator.forEachRemaining(Iterator.java:116) [rt.jar:1.8.0_45-internal]
[Server:server-one] at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) [rt.jar:1
.8.0_45-internal]
[Server:server-one] at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) [rt.jar:1.8.0_45-intern
al]
[Server:server-one] at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) [rt.jar:1.8.0_45
-internal]
[Server:server-one] at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) [rt.jar:1.8.0_4
5-internal]
[Server:server-one] at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) [rt.jar:1
.8.0_45-internal]
[Server:server-one] at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) [rt.jar:1.8.0_45-intern
al]
[Server:server-one] at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) [rt.jar:1.8.0_45-internal]
[Server:server-one] at com.myapp.rest.logic.UserLogic.assignRollesToUser(UserLogic.java:280)
Why am I getting java.util.ConcurrentModificationException?

You must first remove the dependent entities. So if you have an entity Students and each Student has zero or more Classes, and students and classes are joined many-to-many in the table StudentsClasses, you would have a collection of classes for each student and a collection of students for each class. To delete a student, you must first remove their classes from the many-to-many table. So if you have a method RemoveStudentClass that takes a student and a class as a parameter, you do something like
public void RemoveSudent(student deleteStudent)
{
foreach (Class c in deleteSudent.Classes)
{
removeStudentClass(deleteSudent,c);
}
Students.Delete(deleteStudent);
}

Related

Filter IndexedEmbedded entity in hiberenate search 6

Let's say i have a Indexed entity User that IndexedEmbedded a list of Entity Role. In that list we have also past Roles (soft deleted), i wanna index User, end limit roles list to only active roles in my index.
Kind of:
#Entity
#Indexed
public class User{
#FullTextField
String name
#IndexedEmbedded
List<Role> roles
}
public class Role{
#FullTextField
String name
String status //wanna filter status == "DELETED"
}
There is something like RoutingBridge in indexed annotation?
First, a warning: you'd probably want to consider cleaning up associations upon soft-deletion of a role (remove the role from users), because this situation is likely to affect other parts of your application.
With that out of the way, here's a more useful answer...
No, RoutingBridge won't help here.
There's no built-in feature for what you're trying to achieve (yet).
The simplest solution I can think of is adding a "derived" getter that does the filtering.
#Entity
#Indexed
public class User{
#FullTextField
private String name;
#ManyToMany
private List<Role> roles;
#javax.persistence.Transient
#IndexedEmbedded(name = "roles")
// Tell Hibernate Search that changing the status of a Role might
// require reindexing
#IndexingDependency(derivedFrom = #ObjectPath(
#PropertyValue(propertyName = "roles"), #PropertyValue(propertyName = "status")
))
// Tell Hibernate Search how to find users to reindex
// when a Role changes (its name or status)
#AssociationInverseSide(inversePath = #ObjectPath(
#PropertyValue(propertyName = "users")
))
public List<Role> getNonDeletedRoles() {
return roles.stream().filter(r -> !"DELETED".equals(r.getStatus()))
.collect(Collectors.toList());
}
}
#Entity
public class Role{
#FullTextField
private String name;
private String status;
// You will need this!
#ManyToMany(mappedBy = "roles")
private List<User> users;
}
If for some reason you cannot afford to model the association Role => User, you will have to give up on reindexing users when a role changes:
#Entity
#Indexed
public class User{
#FullTextField
private String name;
#ManyToMany
private List<Role> roles;
#javax.persistence.Transient
#IndexedEmbedded(name = "roles")
#IndexingDependency(reindexOnUpdate = ReindexOnUpdate.SHALLOW,
derivedFrom = #ObjectPath(#PropertyValue(propertyName = "roles")))
public List<Role> getNonDeletedRoles() {
return roles.stream().filter(r -> !"DELETED".equals(r.getStatus()))
.collect(Collectors.toList());
}
}
#Entity
public class Role{
#FullTextField
private String name;
private String status;
}

How to avoid LazyInitializationException with nested collections in JPA-Hibernate?

Mandatory background info:
As part of my studies to learn Spring, I built my usual app - a little tool that saves questions and later creates randomized quizzes using them.
Each subject can have any number of topics, which in turn may have any number of questions, which once again in turn may have any number of answers.
Now, the problem proper:
I keep getting LazyInitializationExceptions.
What I tried last:
I changed almost each and every collection type used to Sets.
Also felt tempted to set the enable_lazy_load_no_trans property to true, but I've consistently read this is an antipattern to avoid.
The entities proper: (only fields shown to avoid wall of code-induced fatigue)
Subject:
#Entity
#Table(name = Resources.TABLE_SUBJECTS)
public class Subject implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_SUBJECT)
private int subjectId;
#Column(name="subject_name", nullable = false)
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_SUBJECT,
fetch = FetchType.EAGER
)
private Set<Topic> topics;
}
Topic:
#Entity
#Table(name = Resources.TABLE_TOPICS)
public class Topic implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "topic_id")
private int topicId;
#Column(name = "name")
private String name;
#OneToMany(
mappedBy = Resources.ENTITY_TOPIC,
orphanRemoval = true,
cascade = CascadeType.MERGE,
fetch = FetchType.EAGER
)
private Set<Question> questions;
#ManyToOne(
fetch = FetchType.LAZY
)
private Subject subject;
}
Question:
#Entity
#Table(name = Resources.TABLE_QUESTIONS)
public class Question implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_QUESTION)
private int questionId;
#Column(name = "statement")
private String statement;
#OneToMany(
mappedBy = Resources.ENTITY_QUESTION,
orphanRemoval = true,
cascade = CascadeType.MERGE
)
private Set<Answer> answers;
#ManyToOne(
fetch = FetchType.LAZY
)
private Topic topic;
}
Answer:
#Entity
#Table(name = Resources.TABLE_ANSWERS)
public class Answer implements DomainObject
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = Resources.ID_ANSWER)
private int answerId;
#Column(name = "answer_text", nullable = false)
private String text;
#Column(name = "is_correct", nullable = false)
private Boolean isCorrect;
#ManyToOne(
fetch = FetchType.LAZY
)
private Question question;
}
I'm using interfaces extending JpaRepository to perform CRUD operations. I tried this to fetch stuff, without luck:
public interface SubjectRepository extends JpaRepository<Subject, Integer>
{
#Query
Optional<Subject> findByName(String name);
#Query(value = "SELECT DISTINCT s FROM Subject s " +
"LEFT JOIN FETCH s.topics AS t " +
"JOIN FETCH t.questions AS q " +
"JOIN FETCH q.answers as a")
List<Subject> getSubjects();
}
Now, the big chunk of text Spring Boot deigns to throw at me - the stack trace:
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [org.callisto.quizmaker.domain.Subject#1] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:322) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.6.4.Final.jar:5.6.4.Final]
at org.callisto.quizmaker.domain.Subject$HibernateProxy$B8rwBfBD.getTopics(Unknown Source) ~[main/:na]
at org.callisto.quizmaker.service.QuizMakerService.activeSubjectHasTopics(QuizMakerService.java:122) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.checkIfActiveSubjectHasTopics(QuizMaker.java:307) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.createNewQuestion(QuizMaker.java:117) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.prepareMainMenu(QuizMaker.java:88) ~[main/:na]
at org.callisto.quizmaker.QuizMaker.run(QuizMaker.java:65) ~[main/:na]
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:769) ~[spring-boot-2.6.3.jar:2.6.3]
This exception happens when I call this line of code:
boolean output = service.activeSubjectHasTopics();
Which, in turn, calls this method on a service class:
public boolean activeSubjectHasTopics()
{
if (activeSubject == null)
{
throw new NullPointerException(Resources.EXCEPTION_SUBJECT_NULL);
}
return !activeSubject.getTopics().isEmpty();
}
The activeSubjectHasTopics method gets called in this context:
private void createNewQuestion(View view, QuizMakerService service)
{
int subjectId = chooseOrAddSubject(view, service);
service.setActiveSubject(subjectId);
if (checkIfActiveSubjectHasTopics(view, service))
{
chooseOrAddTopic(view, service, subjectId);
}
do
{
createQuestion(view, service);
createAnswers(view, service);
}
while(view.askToCreateAnotherQuestion());
service.saveDataToFile();
prepareMainMenu(view, service);
}
private boolean checkIfActiveSubjectHasTopics(View view, QuizMakerService service)
{
boolean output = service.activeSubjectHasTopics();
if (!output)
{
view.printNoTopicsWarning(service.getActiveSubjectName());
String topicName = readTopicName(view);
createNewTopic(service, topicName);
}
return output;
}
Not helpful if you change your structure to set. If you need to get the entities you need to explicitly include FETCH clause in your hql queries. You'll work your way by checking out the Hibernate documentation:
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html#performance-fetching
I was able to track down the cause of the issue thanks to a comment from Christian Beikov - to quote:
Where do you get this activeSubject object from? If you don't load it
as part of the transaction within activeSubjectHasTopics, then this
won't work as the object is already detached at this point, since it
was loaded through a different transaction.
The activeSubject object was defined as part of the service class containing the activeSubjectHasTopics method, and was initialized by a different transaction as he pointed out.
I was able to fix the problem by annotating that service class as #Transactional and storing the IDs of the objects I need instead of the objects themselves.

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.

JPA: On delete cascade #OneToMany relationship EclipseLink

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.

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