I have run into a nasty bug with jpa and hibernate. I have a billing class with the following annotation:
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="ch1_id", referencedColumnName="id")
private List<BillingItem>billingItems = new ArrayList<BillingItem>();
Now I need to filter deleted items from the collection but cannot use anything but jpa. No hibernate specific annotations allowed. So I wrote a post load function:
#PostLoad
public void postLoad() {
ArrayList<BillingItem>tempItems = new ArrayList<BillingItem>();
Iterator<BillingItem> i = this.billingItems.iterator();
BillingItem item;
while(i.hasNext()) {
item = i.next();
if( item.getStatus().equals("D")) {
tempItems.add(item);
}
}
this.billingItems.removeAll(tempItems);
}
However when there are deleted items to filter I'm seeing
Hibernate: update billing_on_item set ch1_id=null where ch1_id=? and id=?
which produces an exception because ch1_id is a foreign key and cannot be null. However hibernate is binding the parameters to correct values. Why is this update occurring in the first place? How do I correct the error?
Thanks in advance,
Randy
By removing the items from the collection, you're telling Hibernate that the association between the two entities doesn't exist anymore, so obviously, Hibernate removes what materializes this association in the database: it sets the foreign key to null.
What you probably want is just a getter in your entity that returns only the non-deleted items:
public List<BillingItem> getNonDeletedItems() {
List<BillingItem> result = new ArrayList<BillingItem>();
for (BillingItem item : this.billingItems) {
if (!"D".equals(item.getStatus()) {
result.add(item);
}
}
return result;
}
The #OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) line says that it will cascade ALL updates. Look into CascadeType.
Related
I am new to spring data jpa. I have a scenario where I have to create an entity if not exists or update based on non primary key name.Below is the code i wrote to create new entity,it is working fine,but if an already exists record ,its creating duplicate.How to write a method to update if exists ,i usually get list of records from client.
#Override
#Transactional
public String createNewEntity(List<Transaction> transaction) {
List<Transaction> transaction= transactionRespository.saveAll(transaction);
}
Add in your Transaction Entity on variable called name this for naming as unique:
#Entity
public class Transaction {
...
#Column(name="name", unique=true)
private String name;
...
}
Then you won't be able to add duplicate values for name column.
First, this is from google composite key means
A composite key is a combination of two or more columns in a table that can be used to uniquely identify each row in the table when the columns are combined uniqueness is guaranteed, but when it taken individually it does not guarantee uniqueness.
A composite key with an unique key is a waste.
if you want to update an entity by jpa, you need to have an key to classify if the entity exist already.
#Transactional
public <S extends T> S save(S entity) {
if(this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
There are two ways to handle your problem.
If you can not get id from client on updating, it means that id has lost its original function. Then remove your the annotation #Id on your id field,set name with #Id. And do not set auto generate for it.
I think what you want is an #Column(unique = true,nullable = false) on your name field.
And that is the order to update something.
Transaction t = transactionRepository.findByName(name);
t.set.... //your update
transactionRepository.save(t);
I'm trying to fetch Entity1 by querying on mapped entities to it. I'm using CriteriaBuilder to perform this as shown below
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity1> createQuery = criteriaBuilder.createQuery(Entity1.class);
Root<Entity1> root = createQuery.from(Entity1.class);
Join<Entity1, MappedEntity2> mappedEntity2Join = root.join("mappedEntity2");
createQuery.select(root);
predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get(COL_USER_ID), userId));
// where clause to filter by query params
createQuery.where(predicate).distinct(true);
createQuery.getRestriction();
TypedQuery<Entity1> query = entityManager.createQuery(createQuery);
But In random cases, I found that the query was executed on "Entity2.entities1" without specifying Entity2 in join. My guess is that Entity2 is already available in session and it was lazily initialized with entities1. Because of this Criteria generates a query for Entity2 instead of Entity1.
Is there any way to restrict criteria to query on Entity1? or how to remove the Entity2 from session before executing this particular criteria.
Expected query,
select *
from Entity1 obj1_
inner join mappedEntity1 mObj_ on obj1_.obj_id=mObj_.id
where obj1_.id=?
But the query was generated as,
select *
from entities1_entities2 obj0_
inner join Entity1 obj1_ on obj0_.obj_id=obj1_.id
where obj0_.entity2_id=?
Entities structure:
public class Entity1 {
#ManyToOne
MappedEntity1 mappedEntity1;
#OneToMany
MappedEntity2 mappedEntity2;
#OneToMany
MappedEntity3 mappedEntity3;
}
and
public class Entity2 {
#OneToMany
List<Entity1> entities1;
#OneToOne
MappedEntity2 mappedEntity2;
}
Reference table for Entity1 and Entity2
Table name: entities1_entities2
entity1_id INTEGER NOT NULL,
entity2_id INTEGER NOT NULL,
CONSTRAINT entities1_entities2_entity1_id_fkey FOREIGN KEY (entity1_id)
REFERENCES entity1 (id),
CONSTRAINT entities1_entities2_entity2_id_fkey FOREIGN KEY (entity2_id)
REFERENCES entity2 (id)
I don't think it's random. I'm pretty sure there's something wrong in your mapping.
I can see some thing that don't seem right in your question and you are not showing some information.
The mapping on Entity1 seems wrong, I'm assuming what you mean is:
public class Entity1 {
#ManyToOne
MappedEntity1 mappedEntity1;
#ManyToOne // instead of #OneToMany
MappedEntity2 mappedEntity2;
#ManyToOne // instead of #OneToMany
MappedEntity3 mappedEntity3;
}
And you are not showing the mapping of MappedEntity2, only the mapping of Entity2. So I don't know if the bidirectional association is correct.
Even after all this, I think the problem is that you didn't add the mappedBy attribute to the one-to-many association.
Hibernate is querying entities1_entities2 because you've defined a unidirectional one-to-many in Entity2 and this mapping assumes there is a table called entities1_entities2 mapping the association.
If the association is bidirectional, you need a field in Entity1 like this:
class Entity1 {
#ManyToOne
Entity2 entity2;
...
}
Then you can add the mappedBy attribute to entities1 in Entity2:
public class Entity2 {
#OneToMany(mappedBy="entity2")
List<Entity1> entities1;
...
}
This will generate the correct query when you join the two entities.
Anyway, if you want a better answer you need to improve the question.
First, you need to check whether the old entity exist or not before you go querying new entity. You can directly try pass your the entity to session.delete(), in order to delete that object. There should be an exception if no record found in the database which need to be handled. In fact, we usually don't really get this case. We always delete an existing entity, I mean usual logic is like that; so, no need to do that if already done. You can simply do this,
Entity1 ent = session.load(Entity1.class, '1234');
session.delete(ent);
or you can do this instead,
Entity1 ent = new Entity1('1234'); // used constructor for brevity
session.delete(ent);
By the way, you can also use this version session.delete(String query),
session.delete("from Entity1 e where e.id = '1234'"); // Just found it is deprecated
I'm not 100% sure about this. Try closing the current session and opening another one before executing your search.
session.close();
session = sessionFactory.openSession();
This should clear the previously created (lazy initialized) entities.
Try loading the instance you want to remove and delete it.
private boolean deleteById(Class<?> type, Serializable id) {
Object persistentInstance = session.load(type, id);
if (persistentInstance != null) {
session.delete(persistentInstance);
return true;
}
return false;
}
boolean result = deleteById(Product.class, new Long(41));
If you want to remove any entity from Hibernate session, you can do that in 2 steps :
1-Making sure that Hibernate persisted all pending changes in the database
2-Removing the entities from the persistence context
em.flush(); //Write all pending changes to the DB
em.detach(Entity2);// Remove Entity2 from the persistence context
Most IDEs can handle entities for you. You might be able to find a tool that displays all entities and lets you modify them in your IDE of choice.
Current stack:
Spring Boot 1.5.1
Spring Data JPA 1.11.0
Hibernate Core 5.2.6
Let's say we have the following #Entity structure
#Entity
class Root {
#Id
private Long id;
#OneToMany
#JoinColumn(name = "root_id")
private Set<Child> children
}
#Entity
class Child {
#Id
private Long id;
#OneToMany
#JoinColumn(name = "child_id")
private Set<Grandchild> grandchildren;
}
#Entity
class Grandchild {
#Id
private Long id;
}
When I query for all/specific Root objects Hibernate selects only from the corresponding table and the resulting objects' children Set is null a Hibernate proxy - as it should be.
When I call getChildren() Hibernate correctly initializes the collection but also (unwarrantedly) fetches each Child object's grandchildren Set.
Can someone please explain exactly why this recursive fetching is happening and is there a way to disable it?
I did some more digging and this is what I came up with: it seems to be related to the way Hibernate maps #OneToMany depending on whether the target collection is a List or Set.
private final RootRepo repo;
If the collections are Sets
public void test() {
List<Root> all = repo.findAll(); // SELECT root0_.* FROM root root0_
all.forEach(root -> {
System.out.println(root.getChildren() == null); // false
System.out.println(Hibernate.isInitialized(root.getChildren())); // false
root.getChildren().forEach(child -> {
// SELECT child0_.* FROM children child0_
// SELECT grandchild0_.* FROM grandchildren grandchild0_
System.out.println(child.getGrandchildren() == null); // false
System.out.println(Hibernate.isInitialized(child.getGrandchildren())); // true
child.getGrandChildren().forEach(grandchild -> {});
});
});
}
However, with Lists
public void test() {
List<Root> all = repo.findAll(); // SELECT root0_.* FROM root root0_
all.forEach(root -> {
System.out.println(root.getChildren() == null); // false
System.out.println(Hibernate.isInitialized(root.getChildren())); // false
root.getChildren().forEach(child -> {
// SELECT child0_.* FROM children child0_
System.out.println(child.getGrandchildren() == null); // false
System.out.println(Hibernate.isInitialized(child.getGrandchildren())); // false
child.getGrandChildren().forEach(grandchild -> {
// SELECT grandchild0_.* FROM grandchildren grandchild0_
});
});
});
}
I am a certifiable idiot.
I'm using Lombok to generate getters/setters and the like for my POJOs and its default implementation of #EqualsAndHashCode annotation generates both methods taking into account every field.. including the subcollections.
I am quite surprised that the children of Root are actually null.
The way it works in your situation (please double check if the children are actually set as null), is that when you access the getChildren() (by invoking size() for example on it).. that collection is fetched from the database along with all its eager dependencies.
All the lazy dependencies (grandchildren in this particular case) are instantiated as Proxy objects, but there should be no sql query performed on the database for those (please check that).
Additionally
It never happened to me but just a little thing to remember.. According to the JPA, the lazy loading feature is just a hint to the persistence provider. Even when you set the fetchType as LAZY or in general you expect to have your collection dependencies lazy-loaded by default (which can done while configuring the session factory), the implementation may still decide to do an EAGER fetch:
Defines strategies for fetching data from the database. The EAGER
strategy is a requirement on the persistence provider runtime that
data must be eagerly fetched. The LAZY strategy is a hint to the
persistence provider runtime that data should be fetched lazily when
it is first accessed. The implementation is permitted to eagerly fetch
data for which the LAZY strategy hint has been specified.
I am working on a Spring-MVC project where I am using Hibernate for persistence. In one of the model classes I have a List which I want to persist. I am facing the problem as I don't know which dataType to use in PostgreSQL and do I need instruct hibernate in some way or other that I am trying to persist a List. Performance requirements are not that much of a problem on this list, as it does not get that much action. I am posting some code for reference, kindly let me know. Thanks a lot :
GroupAccount model class :
#Entity
#Table(name="groupaccount")
public class GroupAccount {
#Column(name = "blacklist")
private List<String> blacklist;
public List<String> getBlacklist() {
return blacklist;
}
public void setBlacklist(List<String> blacklist) {
this.blacklist = blacklist;
}
}
I would sometimes require to update the values of the blacklist, so I have a method in DAO which updates the groupAccount, I am pasting it below.
GroupAccountDAOImpl edit function :
#Override
public void editGroupAccount(GroupAccount groupAccount) {
session = this.sessionFactory.getCurrentSession();
GroupAccount groupAccount1 = (GroupAccount)session.get(GroupAccount.class,groupAccount.getGroupId());
if(!(groupAccount1==null)){
groupAccount.setOwnedcanvas(groupAccount1.getOwnedcanvas());
groupAccount.setGroupMembersSet(groupAccount1.getGroupMembersSet());
session.merge(groupAccount);
session.flush();
}
}
One use-case for adding users in blacklist :
List<String> blackListUsers;
blackListUsers = groupAccount.getBlacklist();
blackListUsers.add(memberForBlackListing.getMemberUsername());
groupAccount.setBlacklist(blackListUsers);
this.groupAccountService.editGroupAccount(groupAccount);
removeAllMemberships(memberId);
return true;
Any help would be nice. Thanks a lot. :-)
You can't map List<String> to a single column. For these cases, #ElementCollection is used
#ElementCollection
#CollectionTable(name="blacklist", joinColumns=#JoinColumn(name="group_account_id")
#Column(name = "name")
private List<String> blacklist;
This requires a database table named blacklist with columns name and group_account_id (which will be used as a foreign key to group_account table). Of course, table and column names are customizable.
I have an Entity (Layer) that maps a list of other Entities (Member). This List may have no entries / be null. Yet, when I query for the Entity I get a NOT NULL check constraint error from the database.
It seems to be connected to the NamedQueries as I can read the Entity from DB if I query by id.
#Entity
#NamedQueries({
#NamedQuery(name="getChildLayers",-
query = "SELECT la
FROM Layer la
WHERE la.parent = :parent AND la.deletedDate IS NULL")})
public class Layer extends CommonModel {
/*... other field */
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Layer.class, optional = true)
private Layer parent;
#ManyToMany(fetch = FetchType.LAZY, targetEntity = MyUser.class)
private List<MyUser> members;
public List<MyUser> getMembers() {
return members;
}
public void setMembers(List<MyUser> members) {
this.members = members;
}
/*... other getters and setters */
}
I get this error: integrity constraint violation: NOT NULL check constraint; SYS_CT_10298 table: LAYER_MYUSER column: MEMBERS_ID
I am able to create the entry, though.
When I run my tests then all tests fail that read the Entity (but creation works). If I add the following line in the creation method:
layer.setMembers(new ArrayList<MyUser>());
then the methods that test the alternation of the members work (meaning, I can create a Layer and alter its members by adding and removing elements from the list).
It seems to me that reading the Entity from Database fails whenever there are no Member to the Layer.
I did try adding #JoinColumn(nullable=true) to the field, but it changed nothing.
I import javax.persistence classes.
Example as to how I access the variable (in LayerService)
// this method works as expected
public Layer getById(Long id) {
Session s = sessionFactory.getCurrentSession();
return (Layer)s.get(Layer.class, id);
}
// this does not.
public List<Layer> getChildren(Layer layer) {
Query childrenQuery = sessionFactory.getCurrentSession().getNamedQuery("getChildLayers");
childrenQuery.setParameter("parent", layer);
return (List<Layer>) childrenQuery.list();
}
Code changed after Jason Cs answer:
Layer
...
private final List<OCWUser> members = new ArrayList<>();
...
public void setMembers(List<OCWUser> members) {
this.members.clear();
this.members.addAll(members);
}
Problem still exists.
It can be so simple. I forgot to add #JoinTable
#JoinTable(name = "LAYER_USER", joinColumns = #JoinColumn(nullable = true))
One important thing to be aware of is you shouldn't replace this.members with another list in setMembers unless you know you are doing it before you call persist(). Instead you need to clear this.members then add all the specified elements to it. The reason is that Hibernate can and will use its own proxied / instrumented collections classes when [de]serializing an entity, and you blow that away when overwriting the collection class. You should declare members as final and always initialize it to a non-null empty List.
See for example (3.6 but still relevant): http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/collections.html#collections-persistent, In particular:
Notice how in Example 7.2, “Collection mapping using #OneToMany and
#JoinColumn” the instance variable parts was initialized with an
instance of HashSet. This is the best way to initialize collection
valued properties of newly instantiated (non-persistent) instances.
When you make the instance persistent, by calling persist(), Hibernate
will actually replace the HashSet with an instance of Hibernate's own
implementation of Set.
As long as you are messing with collection fields in this way, any number of strange things can happen.
Also, in general, you want to be careful about stating your invariants and such when accessing collections in this way, as it's easily possible to, e.g., create two Layers that reference the same collection internally, so that actions on one affect the other, or external actions on the passed-in collection affect the layer, e.g. the following code probably doesn't behave like you want it to:
List<MyUser> u = new ArrayList<MyUser>();
Layer a = new Layer();
Layer b = new Layer();
u.add(...);
a.setMembers(u);
b.setMembers(u);
u.clear();
Further, when you persist() one of the layers there, and Hibernate overwrites the field with its own collection class, the behavior then changes as the objects are no longer referencing the same collection:
// not only did u.clear() [possibly undesirably] affect a and b above, but:
session.persist(a);
u.add(...); // ... now it only affects b.