I have two related clases JPA-annotated. Alarm and Status. One Alarm can have one Status.
What I need is to be able to delete one Status and "propagate" a null value to the Alarms that are in that Status that has been deleted.
That is, I need the foreign key to be defined as "on delete set null".
#Entity
public class Alarm {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="sequence")
#SequenceGenerator(name="sequence", sequenceName="alarm_pk_seq")
private Integer id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="idStatus")
private Status status;
// get/set
}
#Entity
public class Status {
#Id
#Column(name="idStatus")
private Integer id;
private String description;
// get/set
}
Example:
Before:
STATUS
id description
1 new
2 assigned
3 closed
ALARMS
id status
1 1
2 2
3 2
After (deleting the status with id=2)
STATUS
id description
1 new
3 closed
ALARMS
id status
1 1
2 NULL
3 NULL
I am using Hibernate and PostgreSQL, automatically generating the database from source code. I have tried with every possible CascadeType with no success.
Is there anything wrong in the code ? Is it possible to do it with JPA ?
Just add that using the Hibernate annotation:
#OnDelete(action=OnDeleteAction.CASCADE)
generates the foreign key as : "ON UPDATE NO ACTION ON DELETE CASCADE;"
But there is no action=OnDeleteAction.SET_NULL
Moreover, I don't like to tie my code to Hibernate if possible (but I can live with it if it works).
This thread discusses it. I can't believe there is not an easy method in JPA (or Hibernate extensions) to generate the foreign key.
In your case, you are generating the database from the classes. This would imply that you wont use the database for other purposes (as doing so would force you to have DDL scripts). That means that have this rule implemented in the database or in the java code is unimportant.
We also know that Hibernate would raise a transient exception in the case where one would delete one or more statuses and try to reference it when committing without a cascade.
Also, the database will be generated using a foreign key constraint.
All that means that the constraint MUST be respected for the application to work.
If your entities are in a jar by themselves, you could add a transient method to the alarm or the status interface to remove a status while respecting the rule.
Also, the programmers, when using the entities will be forced to respect the rule or else the code wont work. But to make the task easier, you could make the relation bidirectional so that the task of tracking down the alarms from the statuses is made easier.
If you can, use an ondelete interceptor/listener to set the alarm.status property to null.
Don't know about other non-hibernate implementations, but here is a JIRA issue I've been following about this in Hibernate...
http://opensource.atlassian.com/projects/hibernate/browse/HHH-2707
Are you sure with your #OneToOne?
It seems to me that you'd rather use #ManyToOne (as a status can be affected to several alarms):
#Entity
public class Alarm {
...
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="idStatus", nullable=true)
private Status status;
...
}
OpenJPA has
#ForeignKey(deleteAction=ForeignKeyAction.NULL)
but there is no standard JPA way to do this (and apparently it is impossible with Hibernate).
Makes me want to go back to JDO.
Related
I have been practicing one-to-one mapping in hibernate and don't understand this particular case. I have to say, the program is working fine and as I intended, but apparently I can omit a perist() call and it still works smoothly. The fact that it's working is good, but I want to know exactly why the call is optional. Let me write some details:
This is the user class, which is supposed to be the owning side of the mapping:
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String name;
#OneToOne
private Ticket ticket;
public User() {}
public User(String name) {
this.name=name;
}
}
And this is the ticket class that's supposed to be the dependent one:
#Data
#Entity
public class Ticket {
#Id
#GeneratedValue(generator="foreignGenerator")
#GenericGenerator(name="foreignGenerator", strategy="foreign",
parameters=#org.hibernate.annotations.Parameter(name="property", value="user"))
private Long id;
#OneToOne(optional = false, mappedBy="ticket")
#PrimaryKeyJoinColumn
private User user;
public Ticket() {
}
public Ticket(User user) {
this.user=user;
}
}
I am trying to test the "shared primary key" strategy in one-to-one mapping. As you can see I have set up the generator with foreign strategy, which is supposed to make Ticket's id the same as it's corresponding User's id.
#Bean
CommandLineRunner loadData() {
return args->{
EntityManager em=emf.createEntityManager();
em.getTransaction().begin();
User user=new User("Test User");
Ticket ticket=new Ticket(user);
//em.persist(user);
user.setTicket(ticket);
em.persist(ticket);
em.getTransaction().commit();
em.close();
//We don't have to call persist on user
};
}
}
This program runs perfectly. Uncommenting the line which calls persist on user makes no difference. I am assuming that persisting ticket, which has it's user property set, automatically saves the user as well. Therefore, the reason it makes no difference is that no matter if user is getting saved or not, it will get persisted when we call ticket.
I want to know if my assumption is correct and any additional links to articles/documentation would be greatly appreaciated. Especially I am wondering about this part that I said above-"I am assuming that persisting ticket, which has it's user property set, automatically saves the user as well." I couldn't find anything that would confirm or deny this. I know that the "shared primary key" approach in one-to-one mapping is the only use case of "foreign" generation strategy, so there are not a lot of posts about it, and whatever posts are there are getting overshadowed by "foreign key" during the search.
Any help regarding this or any other issue that might be wrong with the code provided above would be appreciated. Thanks for taking your time to read this
The JPA specification states this behavior is wrong:
Looking at the 3.0 release:
section "3.2.2. Persisting an Entity Instance" implies user is unmanaged after your persist (you can check with the em.contains method).
Section "3.2.4. Synchronization to the Database" covers the flush/commit which states:
• If X is a managed entity, it is synchronized to the database.
..
◦ For any entity Y referenced by a relationship from X, where the relationship to Y has not been annotated with the cascade element value cascade=PERSIST or cascade=ALL:
▪ If Y is new or removed, an IllegalStateException will be thrown by the flush operation (and the transaction marked for rollback) or the transaction commit will fail.
User is new, so this should be resulting in an exception. That it works might be a glitch in how Hibernate is handling the #PrimaryKeyJoinColumn annotation (speculation on my part) and custom "foreignGenerator".
This is not a pattern I'd suggest you rely on, and should instead call persist to avoid inconsistencies with the behavior on other mapping setups.
I'm updating an existing code that handles the copy or raw data from one table into multiple objects within the same database.
Previously, every kind of object had a generated PK using a sequence for each table.
Something like that :
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
In order to reuse existing IDs from the import table, we removed GeneratedValue for some entities, like that :
#Id
#Column(name = "id")
private Integer id;
For this entity, I did not change my JpaRepository, looking like this :
public interface EntityRepository extends JpaRepository<Entity, Integer> {
<S extends Entity> S save(S entity);
}
Now I'm struggling to understand the following behaviour, within a spring transaction (#Transactional) with the default propagation and isolation level :
With the #GeneratedValue on the entity, when I call entityRepository.save(entity) I can see with Hibernate show sql activated that an insert request is fired (however seems to be only in the cache since the database does not change)
Without the #GeneratedValue on the entity, only a select request is fired (no insert attempt)
This is a big issue when my Entity (without generated value) is mapped to MyOtherEntity (with generated value) in a one or many relationship.
I thus have the following error :
ERROR: insert or update on table "t_other_entity" violates foreign key constraint "other_entity_entity"
Détail : Key (entity_id)=(110) is not present in table "t_entity"
Seems legit since the insert has not been sent for Entity, but why ? Again, if I change the ID of the Entity and use #GeneratedValue I don't get any error.
I'm using Spring Boot 1.5.12, Java 8 and PostgreSQL 9
You're basically switching from automatically assigned identifiers to manually defined ones which has a couple of consequences both on the JPA and Spring Data level.
Database operation timing
On the plain JPA level, the persistence provider doesn't necessarily need to immediately execute a single insert as it doesn't have to obtain an identifier value. That's why it usually delays the execution of the statement until it needs to flush, which is on either an explicit call to EntityManager.flush(), a query execution as that requires the data in the database to be up to date to deliver correct results or transaction commit.
Spring Data JPA repositories automatically use default transactions on the call to save(…). However, if you're calling repositories within a method annotated with #Transactional in turn, the databse interaction might not occur until that method is left.
EntityManager.persist(…) VS. ….merge(…)
JPA requires the EntityManager client code to differentiate between persisting a completely new entity or applying changes to an existing one. Spring Data repositories w ant to free the client code from having to deal with this distinction as business code shouldn't be overloaded with that implementation detail. That means, Spring Data will somehow have to differentiate new entities from existing ones itself. The various strategies are described in the reference documentation.
In case of manually identifiers the default of inspecting the identifier property for null values will not work as the property will never be null by definition. A standard pattern is to tweak the entities to implement Persistable and keep a transient is-new-flag around and use entity callback annotations to flip the flag.
#MappedSuperclass
public abstract class AbstractEntity<ID extends SalespointIdentifier> implements Persistable<ID> {
private #Transient boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
// More code…
}
isNew is declared transient so that it doesn't get persisted. The type implements Persistable so that the Spring Data JPA implementation of the repository's save(…) method will use that. The code above results in entities created from user code using new having the flag set to true, but any kind of database interaction (saving or loading) turning the entity into a existing one, so that save(…) will trigger EntityManager.persist(…) initially but ….merge(…) for all subsequent operations.
I took the chance to create DATAJPA-1600 and added a summary of this description to the reference docs.
When certain non key fields of a entity are generated in the database (for instance, by triggers) a call to persist will not bring back values that the database has just generated. In practice this means that you may need to refresh an entity after persist or merge (and when level 2 cache is enabled you may even need to evict the entity).
Hibernate have a custom annotation #Generated which handles Generated Properties.
// Refresh property 1 on insert and update
#Generated(GenerationTime.ALWAYS)
#Column(insertable = false, updatable = false)
private String property1;
// Refresh property 2 on insert
#Generated(GenerationTime.INSERT)
#Column(insertable = false)
private String property2;
JPA #GeneratedValue only works with primary key properties.
So, my question is if there is a replacement for #Generated on JPA API (maybe on 2.1)? And if there isn't one, what is the best practice to handle non key database generated fields?
I read the specs from the beginning until the end and it is not such thing, nothing comparable with #Generated, sorry , and as you said.
The GeneratedValue annotation may be applied to a primary key property
or field of an entity or mapped superclass in conjunction with the Id
annotation.
What you could do is use Event Listener #PrePersist and #PreUpdate to set some properties by default or generated by utility classes before em persist the object , try that approach it comes to my mind to something similiar.
I am trying to use Netbeans 7.01 to follow a tutorial on JSF 2.0 and JPA. I am using oracle XE and JDBC_6. I used JSF pages from entities wizard to generate my JSF pages. Everything works fine as I can retrive data from the database and display them. However when I attempt to create or update a record in the database, I get this error:
An instance of a null PK has been incorrectly provided for the find operation
How is this caused and how can I solve it?
This basically means that you did the following:
Entity entity = em.find(Entity.class, null);
Note that the PK is null here. To fix your problem, just make sure that it's not null.
This may be because you are running a find operation on an entity that has not been persisted yet. In which situation, the #ID field (if it is autogenerated), will not have a value, ie. it will be null. You are then trying to find the entity, and as #BalusC points out, you are sending a null value into your find method.
It means that when you are trying to persist an entity you are sending the PK of the entity as null.
So you have three options:
Define manually the PK for the Entity.
If your database uses a type like Serial (Informix, MS SQLSERVER, etc) then the value will by autoincremented by the RDMS you can use IDENTITY strategy, so now you can pass null value for your entity's pk.
#Entity
public class Inventory implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
If your database uses a sequences for generate pks (Oracle, Postgresql, etc) then the value be provided by a sequence so you can use:
#Entity
public class Inventory implements Serializable {
#Id
#GeneratedValue(generator="InvSeq")
#SequenceGenerator(name="InvSeq",sequenceName="INV_SEQ", allocationSize=5)
private long id;
For more information you can see: http://wiki.eclipse.org/EclipseLink/Examples/JPA/PrimaryKey
I'm running into a strange problem using the Toplink implementation of JPA. I'm writing a stand-alone application to manage loans.
I define a LOAN class that has a OneToMany (bi-directional) relationship with a NOTICE class. I created a GUI to display all the notices for a particular loan. In another part of my program I can send out late notices which is saved as a NOTICE. However, when I try to display all the notices for a loan, the late notice is NOT appearing. I've checked the database and an entry has been saved but for some reason the notice is not being pulled.
Only when I restart my application does the notice appear.
Because I've defined a OneToMany relationship, I, myself, am not making a "direct" query into the database - I'm letting JPA handle retrieving all the notices for me.
As a fix, I created a query to simply get all notices for a particular loan. This works. However, I thought by defining the OneToMany relationship between the two classes, this should be handled for me. It seems like something is not being refreshed properly... as if an "older" list of notices is being used instead of refreshing from the database?
How are you 'sending the late notice'? There needs to be a statement similar to:
MyLoanInstance.listOfNotices.add(MyNoticeInstance);
even if you already have a
MyNoticeInstance.setLoan(MyLoanInstance);
statement
Without that call, you will have to either completely reload the instance you are working with, or restart your application.
Eclipselink does not automatically update (until reloading everything) the collection on the M side of a 1:M relationship when you modify the 1 side, nor does it update the reference in the 1 side of the 1:M relationship if you modify the collection in the M side.
As a side note, you should consider checking out EclipseLink, it evolved out of what was TopLink, you should be able to directly swap the EclipseLink .jar with the TopLink .jar, if only to recieve a few depreciation warnings.
Eclipse Link
Have you tried to do a flush on your entity manager after you have persisted the NOTICE ?
[ entityManager.flush() ]
I suppose the following
#Entity
public class Loan {
private Integer id;
private List<Notice> noticeList;
#Id
#GeneratedValue
public Integer getId() {
return this.id;
}
#OneToMany(mappedBy="loan")
public List<Notice> getNoticeList() {
return noticeList;
}
}
#Entity
public class Notice {
private Integer id;
private Loan loan;
#Id
#GeneratedValue
public Integer getId() {
return this.id;
}
#ManyToOne(fetch=FetchType.LAZY)
public Loan getLoan() {
return this.loan;
}
}
You have said
In another part of my program I can send out late notices which is saved as a NOTICE
Maybe you have two EntityManager instances (one for each part of your program) so the last Notice saved is not showed because both entityManager does not share the same persistence context (One is not aware of the changes occured in the other). What you can do is clear your persistence context and then retrieve it again as follows
In anther part of your program (after persisting your Notice) call
entityManager.flush();
And when you want to retrive a Loan and its Notice
entityManager.clear();
Query query = entityManager.creatQuery("from Loan l left join fetch l.noticeList where l.id = :id");
query.setParameter("id", yourLoanId);
Check it out and tell me whether it works fine.
regards,