Orphan removal in JPA 2 based on only one plain string - java

I have the following situation:
class Container {
...
String key;
...
}
class Item {
String containerKey;
}
I require a mechanism to automatically delete all items "referencing" containers, something like cascading.
Is there such a mechanism in JPA 2?

No, you'll have to get them all and delete them, or execute a delete query:
delete from Item i where i.containerKey = :containerKey

It's not a JPA related solution, but what I did was to create a DB trigger. So every time when a record is deleted from the first table the deletion from the second one is triggered also.

Related

Cannot save after deleting entity in Hibernate

I want to delete all records with some lineId to save another records with the same lineId(as refresh) but after deleting I can't save anything. There isn't any error, but I don't have my record in database.
When I don't have ma deleting code everything saves correctly.
public void deleteAndSaveEntities(List<Entity> entities, Long lineId){
deleteEntities(lineId);
saveEntities(entities);
}
private void deleteEntities(Long lineId) {
List<Entity> entitiesToDelete = entityRepository.findAllByLineId(lineId);
entityRepository.deleteAll(entitiesToDelete);
}
private void saveEntities(List<Entity> entities) {
entityRepository.saveAll(entities);
}
Actually you want to update the entries that has the lineId. Try it as:
First fetch by find..().
Make related changes on that entries
Then save them.
As thomas mentioned, hibernate reorders the queries within the transaction for performance reasons and executes the delete after the update.
I would commit the transaction between these two operations.
Add a #Transactional over deleteEntities and saveEntities.
But be aware that #Transactional does not work when invoked with in the same object.
You must inject the Service into itself and then call the methods on the self reference

Spring - How to commit inside forEach

I have the following code:
private void sendList(List<Data> myData) {
myData.forEach(x -> {
sendData(x);
})
}
#Transactional
private void sendData(Data myData){
//do some changes in myData object and inserts into table
}
Currently, it commits after the insert is complete for every Data object.
But, I would like to commit every 500 records.
Is possible to do this?
Firstly, you don’t need a loop to save the data. Use saveAll instead. (It’s the power of crud).
Secondly, you should add batching. By default it isn't switched on. Therefore you have to add some params to application.properties:
spring.jpa.properties.hibernate.jdbc.batch_size=500
spring.jpa.properties.hibernate.order_inserts=true
The 1st property tells Hibernate to collect inserts in batches of 500. The order_inserts property tells Hibernate to take the time to group inserts by entity, creating larger batches.
Source

Get just added Object with EntityManagerFactory

I'm developing a java app with MySql database, JPA objects and EntityManagerFactory with EclipseLink to manage the database. Everything works Ok but I have an issue.
One of my JPA objects is like this
public class JPAObject1{
#Id
#GeneratedValue
private int id;
#OneToMany(//things here)
List<JPAObject2> list1;
...
}
So the id field will be autogenerated by the EntityManagerFactory when I store it in the database. Asumming em type EntityManager and object type JPAObject1:
em.getTransaction().begin();
em.persist(object);
em.getTransaction().commit();
//house work closing things
The JPAObject1 is added correctly, I can see all fields in my database. As field id is the key to do the find operation, my question is:
Is there a way to get the last added object on the EntityManager on just the moment it is added?
Because I have others objects that use the JPAObject1 id field as a foreign key and I need that field when just the object is added to the database to link the others, but the only way I know to get it is getting all the JPAObjects and getting the last one in the Collection. So, with a few Objects it won't be a problem but if one process insert on database and another do the same before process 1 does the findAll to get the last added, there will be a coherence error....
I think I've explained it well.
Thanks a lot!
you can use this code
Obejct en = new Obejct ();
en.setxxx("My name");
em.persist(en);
em.flush();
System.out.println(en.getId());
the id genrated after flush
Note that the datas saved to database is a set, not list. So they don't have the order or anything like that, and you can't get the last one you've added. If you want to, pls add a column like date, time..., and the query will be like:
" SELECT * FROM Table ORDER BY dateColumn DESC LIMIT 1"

JPA handle merge() of relationship

I have a unidirectional relation Project -> ProjectType:
#Entity
public class Project extends NamedEntity
{
#ManyToOne(optional = false)
#JoinColumn(name = "TYPE_ID")
private ProjectType type;
}
#Entity
public class ProjectType extends Lookup
{
#Min(0)
private int progressive = 1;
}
Note that there's no cascade.
Now, when I insert a new Project I need to increment the type progressive.
This is what I'm doing inside an EJB, but I'm not sure it's the best approach:
public void create(Project project)
{
em.persist(project);
/* is necessary to merge the type? */
ProjectType type = em.merge(project.getType());
/* is necessary to set the type again? */
project.setType(type);
int progressive = type.getProgressive();
type.setProgressive(progressive + 1);
project.setCode(type.getPrefix() + progressive);
}
I'm using eclipselink 2.6.0, but I'd like to know if there's a implementation independent best practice and/or if there are behavioral differences between persistence providers, about this specific scenario.
UPDATE
to clarify the context when entering EJB create method (it is invoked by a JSF #ManagedBean):
project.projectType is DETACHED
project is NEW
no transaction (I'm using JTA/CMT) is active
I am not asking about the difference between persist() and merge(), I'm asking if either
if em.persist(project) automatically "reattach" project.projectType (I suppose not)
if it is legal the call order: first em.persist(project) then em.merge(projectType) or if it should be inverted
since em.merge(projectType) returns a different instance, if it is required to call project.setType(managedProjectType)
An explaination of "why" this works in a way and not in another is also welcome.
You need merge(...) only to make a transient entity managed by your entity manager. Depending on the implementation of JPA (not sure about EclipseLink) the returned instance of the merge call might be a different copy of the original object.
MyEntity unmanaged = new MyEntity();
MyEntity managed = entityManager.merge(unmanaged);
assert(entityManager.contains(managed)); // true if everything worked out
assert(managed != unmanaged); // probably true, depending on JPA impl.
If you call manage(entity) where entity is already managed, nothing will happen.
Calling persist(entity) will also make your entity managed, but it returns no copy. Instead it merges the original object and it might also call an ID generator (e.g. a sequence), which is not the case when using merge.
See this answer for more details on the difference between persist and merge.
Here's my proposal:
public void create(Project project) {
ProjectType type = project.getType(); // maybe check if null
if (!entityManager.contains(type)) { // type is transient
type = entityManager.merge(type); // or load the type
project.setType(type); // update the reference
}
int progressive = type.getProgressive();
type.setProgressive(progressive + 1); // mark as dirty, update on flush
// set "code" before persisting "project" ...
project.setCode(type.getPrefix() + progressive);
entityManager.persist(project);
// ... now no additional UPDATE is required after the
// INSERT on "project".
}
UPDATE
if em.persist(project) automatically "reattach" project.projectType (I suppose not)
No. You'll probably get an exception (Hibernate does anyway) stating, that you're trying to merge with a transient reference.
Correction: I tested it with Hibernate and got no exception. The project was created with the unmanaged project type (which was managed and then detached before persisting the project). But the project type's progression was not incremented, as expected, since it wasn't managed. So yeah, manage it before persisting the project.
if it is legal the call order: first em.persist(project) then em.merge(projectType) or if it should be inverted
It's best practise to do so. But when both statements are executed within the same batch (before the entity manager gets flushed) it may even work (merging type after persisting project). In my test it worked anyway. But as I said, it's better to merge the entities before persisting new ones.
since em.merge(projectType) returns a different instance, if it is required to call project.setType(managedProjectType)
Yes. See example above. A persistence provider may return the same reference, but it isn't required to. So to be sure, call project.setType(mergedType).
Do you need to merge? Well it depends. According to merge() javadoc:
Merge the state of the given entity into the current persistence
context
How did you get the instance of ProjectType you attach to your Project to? If that instance is already managed then all you need to do is just
type.setProgessive(type.getProgressive() + 1)
and JPA will automatically issue an update effective on next context flush.
Otherwise if the type is not managed then you need to merge it first.
Although not directly related this quesetion has some good insight about persist vs merge: JPA EntityManager: Why use persist() over merge()?
With the call order of em.persist(project) vs em.merge(projectType), you probably should ask yourself what should happen if the type is gone in the database? If you merge the type first it will get re-inserted, if you persist the project first and you have FK constraint the insert will fail (because it's not cascading).
Here in this code. Merge basically store the record in different object, Let's say
One Account pojo is there
Account account =null;
account = entityManager.merge(account);
then you can store the result of this.
But in your code your are using merge different condition like
public void create(Project project)
{
em.persist(project);
/* is necessary to merge the type? */
ProjectType type = em.merge(project.getType());
}
here
Project and ProjectType two different pojo you can use merge for same pojo.
or is there any relationship between in your pojo then also you can use it.

How do you update a foreign key value directly via Hibernate?

I have a couple of objects that are mapped to tables in a database using Hibernate, BatchTransaction and Transaction. BatchTransaction's table (batch_transactions) has a foreign key reference to transactions, named transaction_id.
In the past I have used a batch runner that used internal calls to run the batch transactions and complete the reference from BatchTransaction to Transaction once the transaction is complete. After a Transaction has been inserted, I just call batchTransaction.setTransaction(txn), so I have a #ManyToOne mapping from BatchTransaction to Transaction.
I am changing the batch runner so that it executes its transactions through a Web service. The ID of the newly inserted Transaction will be returned by the service and I'll want to update transaction_id in BatchTransaction directly (rather than using the setter for the Transaction field on BatchTransaction, which would require me to load the newly inserted item unnecessarily).
It seems like the most logical way to do it is to use SQL rather than Hibernate, but I was wondering if there's a more elegant approach. Any ideas?
Here's the basic mapping.
BatchQuery.java
#Entity
#Table(name = "batch_queries")
public class BatchQuery
{
#ManyToOne
#JoinColumn(name = "query_id")
public Query getQuery()
{
return mQuery;
}
}
Query.java
#Entity
#Table(name = "queries")
public class Query
{
}
The idea is to update the query_id column in batch_queries without setting the "query" property on a BatchQuery object.
Using a direct SQL update, or an HQL update, is certainly feasible.
Not seeing the full problem, it looks to me like you might be making a modification to your domain that's worth documenting in your domain. You may be moving to having a BatchTransaction that has as a member just the TransactionId and not the full transaction.
If in other activities, the BatchTransaction will still be needing to hydrate that Transaction, I'd consider adding a separate mapping for the TransactionId, and having that be the managing mapping (make the Transaction association update and insert false).
If BatchTransaction will no longer be concerned with the full Transaction, just remove that association after adding a the TransactionId field.
As you have writeen, we can use SQL to achieve solution for above problem. But i will suggest not to update the primary keys via SQL.
Now, as you are changing the key, which means you are creating alltogether a new object, for this, you can first delete the existing object, with the previous key, and then try to insert a new object with the updated key(in your case transaction_id)

Categories

Resources