Hibernate: how to reattach an externalized entity that has an #ElementCollection - java

Context
Stackoverflow has been invaluable to me over the years, and I wanted to give back by posting the question and the answer for something I spent a significant amount of time on recently.
Background
In my situation, I am storing the serialized JSON of our entities in Memcached. For various reasons, I didn't like the way hibernate's caching (2nd level and query cache) worked, so I decided to write my own caching layer. But this created a problem: there appeared to be no easy way to reattach a previously cached POJO back into a hibernate session, especially if there was an #ElementCollection involved.
Here's an approximate definition of some POJOs I wanted to deserialize from JSON, and reattach to a hibernate session:
#Entity
#Table
public class User extends AbstractModel {
#ElementCollection
#CollectionTable (name = "UserAttribute", joinColumns = { #JoinColumn (name = "UserId") })
#MapKeyColumn (name = "Name")
#Column (name = "Value")
private Map<String, String> attributes = new HashMap<String, String>();
...
}
#Entity
#Table
public class Content extends AbstractModel {
#ManyToOne(cascade = CascadeType.ALL) #JoinColumn (name = "UserId")
private User user;
#ElementCollection
#CollectionTable(name = "ContentAttribute", joinColumns = { #JoinColumn(name = "ContentId") })
#MapKeyColumn(name = "Name")
#Column(name = "Value")
private Map<String, String> attributes = new HashMap<String, String>();
...
}
In my searching for an answer to this question, the closest matches where these:
Is there a way in Hibernate to obtain an entity's loaded PersistentCollection without loading the entire entity object first?
What is the proper way to re-attach detached objects in Hibernate?

Picking up where the posters left off from the links in the question, here's the approximate/relevant code for how I managed to reattach. Below that is an outline of what's going on.
#Repository
public abstract class DaoHibernate<T> implements Dao<T> {
#Override
public T reattach(T entity) {
if (getCurrentSession().contains(entity)) {
return entity;
}
if (entity instanceof User) {
return (T) reattachedUser((User) entity);
}
if (entity instanceof Content) {
Content content = (Content) entity;
User user = content.getUser();
if (!currentSession().contains(user)) {
content.setUser(reattachedUser(user));
}
content.setAttributes(persistentAttributesMap(content.getId(), content.getAttributes(), Content.class);
getCurrentSession().lock(content, LockMode.NONE);
return entity;
}
throw new UnsupportedOperationException("reattach is not supported for entity: " + entity.getClass().getName());
}
private User reattachedUser(User user) {
user.setAttributes(persistentAttributesMap(user.getId(), user.getAttributes(), User.class));
getCurrentSession().lock(user, LockMode.NONE);
return user;
}
#SuppressWarnings ("unchecked")
private Map<String, String> persistentAttributesMap(long id, Map<String, String> attributes, Class clazz) {
SessionFactory sessionFactory = getSessionFactory();
Session currentSession = sessionFactory.getCurrentSession();
String role = clazz.getName() + ".attributes";
CollectionPersister collectionPersister = ((SessionFactoryImplementor) sessionFactory).getCollectionPersister(role);
MapType mapType = (MapType) collectionPersister.getCollectionType();
PersistentMap persistentMap = (PersistentMap) mapType.wrap((SessionImplementor) currentSession, attributes);
persistentMap.setOwner(id);
persistentMap.setSnapshot(id, role, ImmutableMap.copyOf(attributes));
persistentMap.setCurrentSession(null);
return persistentMap;
}
...
}
Walk through
As you can see, we have to ensure we never try to reattach an entity that is already in the current session, or else hibernate will throw an exception. That's why we have to do getCurrentSession().contains(entity) in reattach(). Care must be taken here using contains(), because hibernate will not use entity.hachCode() to lookup the entity, but rather System.identityHashCode(entity), which ensures not only that it is an equivalent instance, but the exact same instance that may already be in the session. In other words, you will have to manage reusing instances appropriately.
As long as associated entities are marked with Cascade.ALL, hibernate should do the right thing. That is, unless you have a hibernate managed collection like our #ElementCollection map of attributes. In this case, we have to manually create a PersistentCollection (PersistentMap, to be precise) and set the right properties on it, as in persistentAttributesMap, or else hibernate will throw an exception. In short, on the PersistentMap, we have to:
Set the owner and snapshot key as the id of the owning entity
Set the snapshot role as the fully qualified entity.property name, as hibernate sees it
Set the snapshot Serializable argument as an immutable copy of the existing collection
Set the session to null so hibernate won't think we're trying to attach it to the existing session twice
To complete the reattachment, call session.lock(entity, LockMode.NONE). At this point, as far as I can tell from my testing, hibernate respects this entity and persists all changes correctly when you call saveOrUpdate().
Caveats
I realize this is not a generic solution for all cases. This was just a quick solution to my specific problem that others can hopefully utilize and improve upon. Software is iterative.

Related

Detached entity passed to persist many to many with extra column

I want to create a many to many relationship with a extra column. I configured this:
#Entity
public class Configuration extends AbstractEntity {
#OneToMany(
mappedBy = "configuration",
cascade=CascadeType.ALL,
orphanRemoval = true
)
#JsonIgnore
private List<ConfigurationQuestion> configurationQuestions = new ArrayList<>();
public void addQuestion(QuestionDTO question) {
ConfigurationQuestion configurationQuestion = new ConfigurationQuestion(this,
question.getQuestion(), question.getResponse());
configurationQuestions.add(configurationQuestion);
}
}
Join table
#Entity
public class ConfigurationQuestion implements Serializable {
#EmbeddedId
private ConfigurationQuestionId id ;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("configurationId")
private Configuration configuration;
#ManyToOne(fetch = FetchType.EAGER)
#MapsId("questionId")
#JoinColumn(name="question_id")
private Question question;
private Integer response;
public ConfigurationQuestion(Configuration configuration, Question question,
Integer response) {
this.configuration = configuration;
this.question = question;
this.response = response;
this.id = new ConfigurationQuestionId(configuration.getId(), question.getId());
}
}
Embedable id
#Embeddable
public class ConfigurationQuestionId implements Serializable {
private Long configurationId;
private Long questionId;
}
In question I don't have any mapping I want to do it one directional. My problem is when I try to save something like this. I want to save a configuration that have as question just ids not the entire object, it is saved separatly.
In Question entity I don't have any mapping I want to do it one directional. My problem is when I try to save something like this. I want to save a configuration that has as question/questions just ids not the entire objects, it is saved separately.
Configuration configuration = new Configuration();
Question question = new Question();
question.setId(1L);
configuration.addQuestion(new QuestionDTO(question, 1));
repository.save(configuration);
I receive the following error. I don't want to cascade the changes to Question entity, if I remove Cascade.ALL from Configuration isn't inserting anything in the join table. Can I do this without making a find for questions?
detached entity passed to persist: Question
If you don't want to Cascade your changes from the Configuration entity to the Question entity you have to save it separately. The JPA provider is right to complain that the entity passed is still detached. It is still just a plain old java object instance, the JPA EntityManager does not know about it.
Try to save the Question separately first before saving the Configuration entity.
If you already have the Question in the database, know its ID, but want to avoid getting it completely you can use the getReference() method, which returns a proxy for your Entity. There will still be some overhead by the JPA provider to verify the instance exists (it should throw an EntityNotFoundException if it does not exist), but should be more lightweight.
If you are using a Spring Data JpaRepository there is a similar method, awkwardly named getOne() which does essentially the same thing (it calls the EntityManager.getReference()).

Spring Data delete function not deleting records

I have the following simple application
Users Entity
#Entity
public class Users implements Serializable {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRole Entity
#Entity
public class UserRole implements Serializable {
#Id
#GeneratedValue
private long id;
private String roleName;
#OneToMany(mappedBy = "userrole", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<UserRoleUser> userRoleUser;
// GETTERS AND SETTERS
}
UserRoleUser Many to many resolver class
#Entity
public class UserRoleUser implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn(name = "fk_userId")
private Users user;
#ManyToOne
#JoinColumn(name = "fk_userroleId")
private UserRole userrole;
// GETTERS AND SETTERS
}
UserRoleUserRepository
#Repository
#Transactional
public interface UserRoleUserRepository extends JpaRepository<UserRoleUser, Long>, QueryDslPredicateExecutor<UserRoleUser>{
}
Main Application class
#SpringBootApplication
#Configuration
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
UserRoleUserRepository userRoleUserRepository = context.getBean(UserRoleUserRepository.class);
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
}
}
On running the main application, the database records in the UserRoleUser table are not being deleted. What could be the issue? I am using Spring Data and QueryDsl.
I have also tried putting the delete functionality on a Controller but still doesn't work.
#RestController
#RequestMapping("/api")
public class DeleteController {
#Autowired
UserRoleUserRepository userRoleUserRepository;
#GetMapping("/delete")
public String delete() {
Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));
for (UserRoleUser userRoleUser : findAll) {
userRoleUserRepository.delete(userRoleUser);
}
return new Date().toString();
}
}
If you need to use the given methods provided by CrudRepository, use the JpaRepository.deleteInBatch(). This solves the problem.
The problem is the entities are still attached and will not be deleted until they become detached. If you delete by their id instead of the entity itself, it will delete them.
One thing I noticed is you are deleting the users one at a time which could lead to a database performance hit as the query will be recreated each time. The easiest thing to do is to add all the ids to a set then delete the set of ids. Something like this:
Set<Integer> idList = new HashSet<>();
for (UserRoleUser userRoleUser : findAll) {
idList.add(userRoleUser.getId());
}
if (!idList.isEmpty()) {
userRoleUserRepository.delete(idList);
}
then in your repository add the delete method
#Modifying
#Query("DELETE FROM UserRoleUser uru WHERE uru.id in ?1")
#Transactional
void delete(Set<Integer> id);
The reason why the child objects (UserRoleUser) are not being deleted upon userRoleUserRepository.delete(userRoleUser) call is that each UserRoleUser points to a Users which in turn holds a #OneToMany reference Set<UserRoleUser> userRoleUser.
As described in this StackOverflow answer, what your JPA implementation (e.g. Hibernate) effectively does is:
The cache takes note of the requested child exclusion
The cache however does not verify any changes in Set<UserRoleUser>
As the parent #OneToMany field has not been updated, no changes are made
A solution would go through first removing the child element from Set<UserRoleUser> and then proceed to userRoleUserRepository.delete(userRoleUser) or userRepository.save(user)
In order to avoid this complication two answers have been provided:
Remove element by Id, by calling userRoleUserRepository.deleteById(userRoleUser.getId()) : in this case the entity structure (and therefore the parent) is not checked before deletion. In the analog case of deleteAll something more convoluted such as userRoleUserRepository.deleteByIdIn(userRoleUserList.stream().map(UserRoleUser::getId).collect(Collectors.toList())) would have to be employed
Convert your CrudRepository to a JpaRepository and use its deleteInBatch(userRoleUserList) method. As explained in this article and this StackOverflow answer the deleteInBatch method tries to delete all records at once, possibly generating a StackOverflow error in the case the number of records is too large. As repo.deleteAll() removes one record at a time this error it minimizes this risk (unless the call is itself inside a #Transactional method)
According to this StackOverflow answer, extra care should be used when recurring to deleteInBatch as it:
Does not cascade to other entities
Does not update the persistence context, requiring it to be cleared (the method bypasses the cache)
Finally , as far as I know , there is no way this could be done by simply calling userRoleUserRepository.delete(userRoleUser) without first updating the parent object. Any updates on this (whether by allowing such behaviour through annotations, configuration or any other means) would be a welcome addition to the answer.

Hibernate Entity proxy initialization

I'm having a problem with a Hibernate entity that does not get initialised.
It seems that it's still returning a not initialised proxy...
If I take a look at my debug info I would expect my entity to be initialised.
But it looks like the following:
entity = {SomeEntity_$$_jvst47c_1e#9192}"SomeEntityImpl#1f3d4adb[id=1,version=0]"
handler = {org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer#9196}
interfaces = {java.lang.Class[2]#9197}
constructed = true
persistentClass = {java.lang.Class#3605}"class SomeEntityImpl"
getIdentifierMethod = null
setIdentifierMethod = null
overridesEquals = true
componentIdType = null
replacement = null
entityName = {java.lang.String#9198}"SomeEntityImpl"
id = {java.lang.Long#9199}"1"
target = {SomeEntityImpl#9200}"SomeEntityImpl#1f3d4adb[guid=<null>,id=1,version=0]"
initialized = true
readOnly = true
unwrap = false
session = {org.hibernate.internal.SessionImpl#6878}"SessionImpl(PersistenceContext[entityKeys=[EntityKey[EntityReferenceImpl#2], EntityKey[SomeEntityImpl#1], EntityKey[...
readOnlyBeforeAttachedToSession = null
sessionFactoryUuid = null
allowLoadOutsideTransaction = false
Notice that my Hibernate POJO still only contains a handlereven after doing an explicit initialisation...
In my debug view, I can see the 'real' property values (not displayed above) when I expand the target node.
What I'm doing:
EntityReferenceImpl entityReference = findEntityReference(session);
SomeEntity entity = null;
if (entityReference != null) {
// initialize association using a left outer join
HibernateUtil.initialize(entityReference.getSomeEntity());
entity = entityReference.getSomeEntity();
}
return entity;
Notice the HibernateUtil.initialize call!
SomeEntity mapping:
public class SomeEntityImpl extends AbstractEntity implements SomeEntity {
#OneToMany(mappedBy = "someEntity", fetch = FetchType.EAGER, targetEntity = EntityReferenceImpl.class, orphanRemoval = true)
#Cascade(CascadeType.ALL)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<EntityReference> entityReferences = new HashSet<>();
#Target(EntityName.class)
#Embedded
private Name name;
#Target(EntityAddress.class)
#Embedded
private Address address;
...
}
EntityReferenceImpl mapping:
public class EntityReferenceImpl extends AbstractEntity implements EntityReference {
#ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = SomeEntityImpl.class)
#JoinColumn(name = "entity_id")
private SomeEntity someEntity;
...
}
So what is the side effect: When the POJO later comes with updated properties I'm still having the same structure (as mentioned above) and I can see the updated properties under the target node.
But when I'm trying to update the entity using session.merge() or session.update()or session.saveOrUpdate(), Hibernate does not detect the 'dirty' properties and does not invoke an update query to the database.
Does anyone have some clues about this weird behavior? I have tried everything what I can but without any results.
All help is very welcome!!
Entity in your debug window looks like properly initialized.
When you have some entity that may be proxied by hibernate, this entity is stored inside proxy object even after being properly initialized. After initialisation proxy object itself doesn't disappear...
public class EntityReferenceImpl extends AbstractEntity implements EntityReference {
#ManyToOne(fetch = FetchType.LAZY, ...)
private SomeEntity someEntity;
...
In your example you have EntityReferenceImpl entity which has #ManyToOne(LAZY) to SomeEntity entity.
When hibernate loads EntityReferenceImpl it fills all fields from resultSet values but someEntity field is set to proxy object.
This proxy objects looks like this:
class SomeEntity_$$_javassist_3 extends SomeEntity implements HibernateProxy {
+ firstname = NULL;
+ lastname = NULL;
+ age = 0;
+ handler; //of type: JavassistLazyInitializer
getFirstname() {
handler.invoke(..., Method thisMethod, Method proceed, args);
}
getLastName() {...}
}
Your SomeEntity class has (for example) methods getFirstName() etc, but javassist generated class simply extends your SomeEntity and has few new bytecode-generated methods like c7getFirstName() etc.
And most important - proxy class has new field: handler of type JavassistLazyInitializer.
Lets see how JavassistLazyInitializer looks like:
JavassistLazyInitializer {
+ target; //holds SomeEntity object
invoke(..., Method thisMethod, Method proceed, args) {
if (target == null) {
target = initialize(); // calls sessionImpl.immediateLoad
}
return thisMethod.invoke( target, args );
}
}
So when you look into your proxy object - it has your fields like firstname, lastname etc.
When you initialize this proxy, SomeEntity is loaded into target field. Your firstname, lastname fields on proxy objects are null as before - proxy doesn't use them, but real data is in SomeEntity object held by target field.
This is how proxy is implemented in hibernate.
You may ask - why such solution? Such design comes from polymorphism issues. If SomeEntity would be abstract parent class with 2 subclasses EntityA and EntityB hibernate has no problem - someEntity field holds proxy (generated) class extending SomeEntity but having concrete EntityA or EntityB inside target field.
However there are some pitfalls with this solution and polymorphism. Your someEntity field will be instance of SomeEntity but never instance of EntityA nor instance of EntityB.
Hibernate uses Proxies to intercept calls to LAZY entities. That structure you see in debug is how a Proxy looks like.
You don't need to call HibernateUtil.initialize, but simply use "fetch joins" to load all entities you are interested in a single query.
If the entity is attached to the current Session, the dirty checking mechanism will automatically translate all entity state transitions to database DML statements.
Session.update is meant to re-attach detached entities (entities that were loaded in a Session that's been closed).
Session.merge is for copying the entity state onto an already loaded entity (which is loaded on the fly, if not loaded previously).
Check if you have enabled transactions, as otherwise you can only select entities. For persist/merge and dirty checking updates you must use transactions (use Java EE or Spring #Transactional support).
The post https://forum.hibernate.org/viewtopic.php?f=1&t=997278 was useful for me. As the getter methods in Entity Model Objects were marked as final, Javaassist wasn't able to override the method and thus change it's value. My Entity Objects were like this -
#Entity
#Table(name = "COUNTRIES")
public class Country {
#Id
private Long id;
#Column(nullable = false)
private String name;
private final getId() {
return id;
}
private final getName() {
return name;
}
}

How to clone a JPA entity

I have a JPA entity already persisted in the database.
I would like to have a copy of it (with a different id), with some fields modified.
What is the easiest way to do this? Like:
setting it's #Id field to null and persisting it will work?
will I have to create a clone method for the entity (copying all fields except the #Id)?
is there any other approach (like using a cloning framework)?
Use EntityManager.detach. It makes the bean no longer linked to the EntityManager. Then set the Id to the new Id (or null if automatic), change the fields that you need and persist.
You are better off using a copy constructor and controlling exactly what attributes need to be cloned.
So, if you have a Post entity like this one:
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
#OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
#ManyToMany
#JoinTable(
name = "post_tag",
joinColumns = #JoinColumn(
name = "post_id"
),
inverseJoinColumns = #JoinColumn(
name = "tag_id"
)
)
private Set<Tag> tags = new HashSet<>();
//Getters and setters omitted for brevity
public void addComment(
PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void addDetails(
PostDetails details) {
this.details = details;
details.setPost(this);
}
public void removeDetails() {
this.details.setPost(null);
this.details = null;
}
}
It does not make sense to clone the comments when duplicating a Post and using it as a template for a new one:
Post post = entityManager.createQuery("""
select p
from Post p
join fetch p.details
join fetch p.tags
where p.title = :title
""", Post.class)
.setParameter(
"title",
"High-Performance Java Persistence, 1st edition"
)
.getSingleResult();
Post postClone = new Post(post);
postClone.setTitle(
postClone.getTitle().replace("1st", "2nd")
);
entityManager.persist(postClone);
What you need to add to the Post entity is a copy constructor:
/**
* Needed by Hibernate when hydrating the entity
* from the JDBC ResultSet
*/
private Post() {}
public Post(Post post) {
this.title = post.title;
addDetails(
new PostDetails(post.details)
);
tags.addAll(post.getTags());
}
This is the best way to address the entity clone/duplication problem. Any other methods, which try to make this process completely automatic, miss the point that not all attributes are worth duplicating.
When using EclipseLink, you can use the VERY handy CopyGroup-Feature:
http://wiki.eclipse.org/EclipseLink/Examples/JPA/AttributeGroup#CopyGroup
A big plus is that without much fiddling it properly clones private-owned relation-ships, too.
This is my code, cloning a Playlist with its private-owned #OneToMany-relationship is a matter of a few lines:
public Playlist cloneEntity( EntityManager em ) {
CopyGroup group = new CopyGroup();
group.setShouldResetPrimaryKey( true );
Playlist copy = (Playlist)em.unwrap( JpaEntityManager.class ).copy( this, group );
return copy;
}
Make sure that you use persist() to save this new object, merge() does not work.
I face the same problem today : I have an entity in database and I want to :
get it from database
change one of its attributes value
create a clone of it
modify just some few attributes of the clone
persist clone in database
I succeed in doing following steps :
#PersistenceContext(unitName = "...")
private EntityManager entityManager;
public void findUpdateCloneAndModify(int myEntityId) {
// retrieve entity from database
MyEntity myEntity = entityManager.find(MyEntity.class, myEntityId);
// modify the entity
myEntity.setAnAttribute(newValue);
// update modification in database
myEntity = entityManager.merge(myEntity);
// detach entity to use it as a new entity (clone)
entityManager.detach(myEntity);
myEntity.setId(0);
// modify detached entity
myEntity.setAnotherAttribute(otherValue);
// persist modified clone in database
myEntity = entityManager.merge(myEntity);
}
Remark : last step (clone persistence) does not work if I use 'persist' instead of 'merge', even if I note in debug mode that clone id has been changed after 'persist' command !
The problem I still face is that my first entity has not been modified before I detach it.
You could use mapping frameworks like Orika. http://orika-mapper.github.io/orika-docs/
Orika is a Java bean mapping framework that recursively copies data from one object to another. It is easy to configure and provides various flexibilities as well.
Here is how I have used it in my project:
added a dependecy :
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.4.6</version>
</dependency>
Then use it in the code as follows:
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper=mapperFactory.getMapperFacade();
User mappedUser = mapper.map(oldUser, User.class);
This might help if you have many usecases where such kind of cloning is needed.
As mentioned in the comments to the accepted answer, detatch will ignore unflushed changes to the managed entity.
If you are using spring you have another option which is to use org.springframework.beans.BeanUtils
Here you have BeanUtils.copyProperties(Object source, Object target). This will allow you to do a shallow copy without tampering with the entityManager.
Edit:
quote from api doc: "this method is intended to perform a "shallow copy" of the properties and so complex properties (for example, nested ones) will not be copied."
This blog post may inform you more about deep copying java objects.
I just tried setting the id to null and it worked
address.setId(null);
address = addrRepo.save(address);
setting the id to null made it so it saved into a new record with new id since i have it automatically generated.
ModelMapper lib can be used for this purpose.
public MyEntity clone(MyEntity myInstance) {
MyEntity newInstance = new MyEntity();
new ModelMapper().map(myInstance, newInstance);
return newInstance;
}
just add the maven dependency
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.2</version>
</dependency>

Hibernate: enforcing unique data members

I am having an issue working with Hibernate and enforcing unique data members when inserting.
Here are my abridged Entity objects:
Workflow:
#Entity
public class Workflow {
private long wfId;
private Set<Service> services;
/** Getter/Setter for wfId */
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "workflow_services",
joinColumns = #JoinColumn(name = "workflow_id"),
inverseJoinColumns = #JoinColumn(name = "service_id"))
public Set<Service> getServices() {
return services;
}
Service:
#Entity
public class Service {
private long serviceId;
private String serviceName;
/** Getter/Setter for serviceId */
...
#Column(unique=true,nullable=false)
public String getServiceName() {
return serviceName;
}
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "service_operations",
joinColumns = { #JoinColumn(name = "serviceId") },
inverseJoinColumns = { #JoinColumn(name = "operationId") })
public Set<Operation> getOperations() {
return operations;
}
Operation:
#Entity
public class Operation {
private long operationId;
private String operationName;
/** Getter/Setter for operationId */
#Column(unique=true,nullable=false)
public String getOperationName() {
return operationName;
}
My issue:
Although I have stated in each object what is SUPPOSED to be unique, it is not being enforced.
Inside my Workflow object, I maintain a Set of Services. Each Service maintains a list of Operations. When a Workflow is saved to the database, I need it to check if the Services and Operations it currently uses are already in the database, if so, associate itself with those rows.
Currently I am getting repeats within my Services and Operations tables.
I have tried using the annotation:
#Table( uniqueConstraints)
but have had zero luck with it.
Any help would be greatly appreciated
The unique or uniqueConstraints attributes are not used to enforce the uniqueness in the DB, but create the correct DDL if you generate it from hibernate (and for documentation too, but that's arguable).
If you declare something as unique in hibernate, you should declare it too in the DB, by adding a constraint.
Taking this to the extreme, you can create a mapping in which the PK is not unique in the DB, and hibernate will throw an exception when it tries to load one item by calling Session.load, and sudently finding that there are 2 items.
Inside my Workflow object, I maintain a Set of Services. Each Service maintains a list of Operations. When a Workflow is saved to the database, I need it to check if the Services and Operations it currently uses are already in the database, if so, associate itself with those rows.
I think you're asking Hibernate to detect duplicate objects when you add them to the Set, yes? In other words, when you put an object in the Set, you want Hibernate to go look for a persistent version of that object and use it. However, this is not the way Hibernate works. If you want it to "reuse" an object, you have to look it up yourself and then use it. Hibernate doesn't do this.
I would suggest having a helper method on a DAO-like object that takes the parent and the child object, and then does the lookup and setting for you.

Categories

Resources