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.
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 using JPA with Hibernate 5.2.10.Final (Oracle database), and deploying on Weblogic 12.2.1.
Let's say I have 2 tables: Customer and LastActivity:
Customer {
id int,
name String,
last_activity_id int not null
}
LastActivity {
id int,
customerName String,
date Date
}
There is a One to Many relationship: a Customer has a single Activity and one Activity has many Customers.
I have a functionality of adding a Customer, when it happens the record in LastActivity table must be created if it doesn't exist for that Customer, otherwise the date must be updated.
My code looks like this (simplified for the purpose of the question):
public Response createCustomer(Request request) {
String name = request.getName();
Customer customer = new Customer(name);
LastActivity activity = activityDao.findByCustomerName(name)
.orElseGet(LastActivity.from(name));
activity.setDate(ZonedDateTime.now());
customer.setActivity(activityDao.update(activity));
return Response.of(customer);
}
My update method is straightforward:
return entityManager.merge(entity);
When I add a new Customer and an Activity that doesn't exist yet ― it is created correctly with the date I specified. The problem is when the activity already exists ― the update doesn't happen. In the logs there is just a select query on Activities table, then correct insert on Customers table, but the date is old.
Some things I tried:
public T update(T entity) {
EntityManager manager = getEntityManager();
T updated = manager.contains(entity) ? entity : manager.persist(entity);
manager.flush();
return updated;
}
Same thing, nothing changed. Also:
Without flushing
Doing merge instead of just returning entity when contains returns true
Just a flush by itself
Nothing since the entity is "attached"
Tried adding CascadeType.MERGE...still nothing. Only thing that worked was this:
public T update(T entity) {
EntityManager manager = getEntityManager();
manager.detach(entity);
return manager.merge(entity);
}
It did what I wanted it to do, but it added extra select query on Activity table (simply by ID, but still, I would like to avoid that).
I actually managed to "solve" the problem by using CriteriaUpdate, but I don't like this and it seems like I lack of some fundamental knowledge about JPA/Hibernate so I don't just want to leave it like this.
I've the following Entity:
class User {
..
...
#OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, mappedBy = "id.uKey")
#MapKey(name="id.achieveId")
private Map<Integer, Achievement> achievements;
..
..
}
at some point I call:
Hibernate.Initialize();
and this map is filled with entries from DB.
when the app continues I save new entries into the DB table.
and then I try to access the map but it doesn't contain the new entries.
Is there a way to make it aware that it needs to re-select the DB table?
Thanks
EDIT:
I add new entries like this:
public void save() {
..
tx = dbs.beginTransaction();
Achievement ua = new Achievement(key, id);
dbs.save(ua);
tx.commit();
}
After initializing your object, it resides in the hibernate session and this session is not designed to be updated by changes from the underlying database. Just reload the object from the database, but remove it from the session before doing so.
But maybe, what you really want is to modify the map contained in your object. That would be the OOP way. After that you could persist the entire object with Hibernate.
I have 2 entities with many-to-many relationship. The Movie Entity is the owner of this relation, so when I want to delete an Actor Entity I use a method annotated #PreRemove to delete any occurrences of Actor ID in Movie cast to avoid "Foreign key violation exception".
Movie class
#Entity
public class Movie extends AbstractBusinessObject{
#ManyToMany
private Map<String, Actor> cast;
// setters and getters
public void removeCastMember(Actor actor){
for (Entry<String, Actor> e : cast.entrySet()) {
if(e.getValue().id.equals(actor.id)){
cast.remove(e.getKey());
}
}
} // removeCastMember()
}
Actor class
#Entity
public class Actor extends AbstractBusinessObject{
#ManyToMany(mappedBy = "cast")
private Set<Movie> movies;
// setters and getters
#PreRemove
private void removeActorFromMovies() {
for (Movie m : movies) {
m.removeCastMember(this);
}
}
}
To be clear, from my testing, it works - movie objects are correctly updated in the database. However, I cannot understand how is it possible when there are no calls to saveOrUpdate() or persist/merge those objects.
That's a fundamental feature of JPA/Hibernate. All the changes made to attached entities are automatically made persistent: Hibernate manages them, so it compares their current state with their initial state, and automatically makes all the changes persistent.
This is extremely useful, because you don't have to track all the entities that have been modified in a complex business method modifying lots of entities. And it's also efficient because Hibernate won't execute unnecessary SQL: if an entity hasn't changed during the transaction, no SQL update query will be executed for this entity. And if you modify entities and then throw an exception rollbacking the transaction, Hibernate will skip the updates.
So, typical JPA code would look like this:
void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
Account from = em.find(Account.class, fromAccountId); // from is managed by JPA
Account to = em.find(Account.class, ftoAccountId); // to is managed by JPA
from.remove(amount);
to.add(amount);
// now the transaction ends, Hibernate sees that the state of from and to
// has changed, and it saves the entities automatically before the commit
}
persist() is used to make a new entity persistent, i.e. to make it managed by Hibernate.
merge() is used to take a detached entity (i.e. an entity which is not managed by Hibernate, but already has an ID and a state) and to copy its state to the attached entity having the same ID.
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.