If I have an entity such as the following:
#Entity
public class Customer {
private Address address;
}
And the Address is also an entity:
#Entity
public class Address {...}
Does persisting the Customer in turn persist its contained Address? Or is this not possible at all? The idea was basically to have a main entity that consists of its fields, some of which are entities themselves that will be stored in individual tables. Some of the fields of Customer are unique in that I also would like a Customer table for that data. Unless I'm just missing it, I haven't been able to find this answer. This was something I was just curious about and I'm not currently on a machine where I can try it, so I wanted to ask first.
Thanks in advance.
This is possible and JPA basics. But you have to define the associations between entities in your entity classes.
I recommend reading a good tutorial on this topic, e.g. the Java EE6 tutorial.
You have 2 options depending on your domain model:
removing the #Entity from address and annotate it with #Embeddable
mapping the Address in the Person with: #OneToOne(cascade = {CascadeType.PERSIST})
Related
I am implementing solution to office relocation. One of the main task is to show where assets/furniture should be moved. I have an entity like this (shorted version):
#Entity
#Table
class Asset extends BaseEntity{
private String name;
#Embedded
private Localization localization;
Localization contains Floor (floor_id, floor_name), x_axis, y_axis;
Here is a graphic representation of my problem (on frontend side).
How I can keep current and next (destination) localization in database? I've tried some ways but they did not work. Like here:
#Entity
#Table
class Asset extends BaseEntity{
private String name;
#AttributeOverrides({
#AttributeOverride(name="floor.id", column=#Column(name="current_floor_id")),
})
#Embedded
private Localization localization;
#AttributeOverrides({
#AttributeOverride(name="floor.id", column=#Column(name="destination_floor_id")),
})
#Embedded
private Localization destinationLocalization;
But it yells at me that floor_id is not unique and should be inserted=false and updatable=false.
Another way that I've tried was #OneToOne relation with new entity Column, or even joining in query without relations.
I've heard about Hibernate Envers, maybe that is solution?
If you need just 2 locations, I would suggest you use the approach you already mentioned i.e. keep both old and current location infos in the entity.
If you need a history, you could create an entity e.g. AssetLocation with a surrogate id and refer to that in the Asset. Just don't delete the old AssetLocation objects, then you can create a one-to-many association to refer to the old positions.
You could use envers as well, but there are some limitations you have to be aware of which you can read about in the documentation.
Working with Spring Data REST, if you have a OneToMany or ManyToOne relationship, the PUT operation returns 200 on the "non-owning" entity but does not actually persist the joined resource.
Example Entities:
#Entity(name = 'author')
#ToString
class AuthorEntity implements Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id
String fullName
#ManyToMany(mappedBy = 'authors')
Set<BookEntity> books
}
#Entity(name = 'book')
#EqualsAndHashCode
class BookEntity implements Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id
#Column(nullable = false)
String title
#Column(nullable = false)
String isbn
#Column(nullable = false)
String publisher
#ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
Set<AuthorEntity> authors
}
If you back them with a PagingAndSortingRepository, you can GET a Book, follow the authors link on the book and do a PUT with the URI of a author to associate with. You cannot go the other way.
If you do a GET on an Author and do a PUT on its books link, the response returns 200, but the relationship is never persisted.
Is this the expected behavior?
tl;dr
The key to that is not so much anything in Spring Data REST - as you can easily get it to work in your scenario - but making sure that your model keeps both ends of the association in sync.
The problem
The problem you see here arises from the fact that Spring Data REST basically modifies the books property of your AuthorEntity. That itself doesn't reflect this update in the authors property of the BookEntity. This has to be worked around manually, which is not a constraint that Spring Data REST makes up but the way that JPA works in general. You will be able to reproduce the erroneous behavior by simply invoking setters manually and trying to persist the result.
How to solve this?
If removing the bi-directional association is not an option (see below on why I'd recommend this) the only way to make this work is to make sure changes to the association are reflected on both sides. Usually people take care of this by manually adding the author to the BookEntity when a book is added:
class AuthorEntity {
void add(BookEntity book) {
this.books.add(book);
if (!book.getAuthors().contains(this)) {
book.add(this);
}
}
}
The additional if clause would've to be added on the BookEntity side as well if you want to make sure that changes from the other side are propagated, too. The if is basically required as otherwise the two methods would constantly call themselves.
Spring Data REST, by default uses field access so that theres actually no method that you can put this logic into. One option would be to switch to property access and put the logic into the setters. Another option is to use a method annotated with #PreUpdate/#PrePersist that iterates over the entities and makes sure the modifications are reflected on both sides.
Removing the root cause of the issue
As you can see, this adds quite a lot of complexity to the domain model. As I joked on Twitter yesterday:
#1 rule of bi-directional associations: don't use them… :)
It usually simplifies the matter if you try not to use bi-directional relationship whenever possible and rather fall back to a repository to obtain all the entities that make up the backside of the association.
A good heuristics to determine which side to cut is to think about which side of the association is really core and crucial to the domain you're modeling. In your case I'd argue that it's perfectly fine for an author to exist with no books written by her. On the flip side, a book without an author doesn't make too much sense at all. So I'd keep the authors property in BookEntity but introduce the following method on the BookRepository:
interface BookRepository extends Repository<Book, Long> {
List<Book> findByAuthor(Author author);
}
Yes, that requires all clients that previously could just have invoked author.getBooks() to now work with a repository. But on the positive side you've removed all the cruft from your domain objects and created a clear dependency direction from book to author along the way. Books depend on authors but not the other way round.
I faced a similar problem, while sending my POJO(containing bi-directional mapping #OneToMany and #ManyToOne) as JSON via REST api, the data was persisted in both the parent and child entities but the foreign key relation was not established. This happens because bidirectional associations need to be manually maintained.
JPA provides an annotation #PrePersist which can be used to make sure that the method annotated with it is executed before the entity is persisted. Since, JPA first inserts the parent entity to the database followed by the child entity, I included a method annotated with #PrePersist which would iterate through the list of child entities and manually set the parent entity to it.
In your case it would be something like this:
class AuthorEntitiy {
#PrePersist
public void populateBooks {
for(BookEntity book : books)
book.addToAuthorList(this);
}
}
class BookEntity {
#PrePersist
public void populateAuthors {
for(AuthorEntity author : authors)
author.addToBookList(this);
}
}
After this you might get an infinite recursion error, to avoid that annotate your parent class with #JsonManagedReference and your child class with #JsonBackReference. This solution worked for me, hopefully it will work for you too.
This link has a very good tutorial on how you can navigate the recursion problem:Bidirectional Relationships
I was able to use #JsonManagedReference and #JsonBackReference and it worked like a charm
I believe one can also utilize #RepositoryEventHandler by adding a #BeforeLinkSave handler to cross link the bidirectional relation between entities. This seems to be working for me.
#Component
#RepositoryEventHandler
public class BiDirectionalLinkHandler {
#HandleBeforeLinkSave
public void crossLink(Author author, Collection<Books> books) {
for (Book b : books) {
b.setAuthor(author);
}
}
}
Note: #HandlBeforeLinkSave is called based on the first parameter, if you have multiple relations in your equivalent of an Author class, the second param should be Object and you will need to test within the method for the different relation types.
I'm developing a code generator that have to generate JPA entities from database meta-model files. These model are from home-brewed modeling system which are being used to generate models other than JPA entities.
In these models some fields are mapping back to same database column. But it seems like JPA does not like that very much. When I try to run generated code I get
Exception [EclipseLink-48] (Eclipse Persistence Services - 2.6.0.v20140809-296a69f): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: Multiple writable mappings exist for the field [FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]. Only one may be defined as writable, all others must be specified read-only.
Mapping: org.eclipse.persistence.mappings.DirectToFieldMapping[TransactionIdKey-->FACT_INVENT_TRANS_HIST_DM.TRANSACTION_ID]
Descriptor: RelationalDescriptor(InventTransHistFactDM --> [DatabaseTable(FACT_INVENT_TRANS_HIST_DM)])
As I can't change the models only option left is to make one of those fields read-only. And the JPA entities being generated are only used to read data from database it will not used for writing data. Is there a way to mark some fields as read only or tell EclipseLink that these entities are read only so it does not have to worry about the multiple writable mapping.
I tried using EclipseLink's #ReadOnly annotation in all entities but it did not help this issue.
There is no #ReadOnly in JPA.
There are however attributes "insertable"/"updatable" that you can set against a field via #Column to effectively do the same.
The question may be almost 6 years old, but it's still being found today, so I'd like to address another option:
public class Foobar {
#OneToOne
#JoinColumn(name="SELF_COLUMN_FOO", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Foo foo;
#OneToOne
#JoinColumn(name="SELF_COLUMN_BAR", referencedColumnName = "FOREIGN_COLUMN_TO_JOIN")
public Bar bar;
}
This can be used where SELF_COLUMN is obviously the relevant column in the Foobar table, and FOREIGN_COLUMN_TO_JOIN would be single key in the other table you wish to join.
This will be useful where you want to have two (or more) attributes in a single class, but only one column to join on the foreign DB table. For example: An Employee may have a home phone number, cell number, and a work phone number. All are mapped to different attributes in the class, but on the database there's a single table of phone numbers and id's, and an identifier column, say VARCHAR(1) with 'H' or 'W' or 'C'. The real example would then be...
Tables:
PHONENUMBERS
PHONENUMBER_ID,
ACTUAL_NUMBER
EMPLOYEE
ID
HOMENUMBER VARCHAR(12),
CELLNUMBER VARCHAR(12),
WORKNUMBER VARCHAR(12)
public class Employee {
#OneToOne
#JoinColumn(name="HOMENUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone homeNum;
#OneToOne
#JoinColumn(name="CELLNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone cellNum;
#OneToOne
#JoinColumn(name="WORKNUMBER", referencedColumnName = "PHONENUMBER_ID")
public Phone workNum;
}
As you can see, this would require multiple columns on the Entity's table, but allows you to reference a foreign key multiple times without throwing the 'Multiple writable mappings exist...' that you showed above. Not a perfect solve, but helpful for those encountering the same problem.
I have an Entity that holds the last instance of a Component:
#Entity
public class Customer {
...
#Embedded
public ServiceCall getLastServiceCall() {...}
...
}
#Embeddable
public class ServiceCall() {
public Date getDate();
public Status getStatus();
}
The ServiceCall is embedded in order to call customer.getLastServiceCall().getDate() without requiring another table join.
I want to make that association one-to-many, and start saving all ServiceCalls, while holding one embedded in the customer.
Hibernate's Docs (v3.6, which I'm using) states:
You can also use association annotations in an embeddable object (ie #OneToOne, #ManyToOne, #OneToMany or #ManyToMany). To override the association columns you can use #AssociationOverride.
and it seem that all I should do is add #OneToMany to the LastServiceCall association.
Will that work for me? If not, what are my alternatives? If yes, how will that affect 2nd level cache, and is there a limitation on updating that embedded instance (I can live with an immutable objects)?
#Embeded types are not supposed to have their own identity in the database, so I don't think you can add #OneToMany to the Customer class on the ServiceCall.
#OneToMany
#Embedded
public ServiceCall getLastServiceCall() {...}
However you can add an association to the #Embeded Service call element like so.
#Entity
pubic class HistoricalServiceCall extends ServiceCall
{
#Id
private String id;
}
#Embeddable
public class ServiceCall {
#OneToMany(fetch=FetchType.LAZY)
#JoinColumn(name="join_column_defined_on_customer_table")
List<HistoricalServiceCall> getServiceCallHistory();
}
Update: putting FetchType.LAZY on the getServiceCallHistory() is a hint to the JPA provider to wait until you call getServiceCallHistory before it does another select to pull in that association.
So with the setup I am describing if you do customer.getLastServiceCall().getDate() it will not pull
in the ServiceCallHistory before the relationship is lazy.
What you need is the following:
A Customer entity
An embeddable ServiceCall
A HistoricalServiceCall entity.
The Customer should contain an embedded ServiceCall field (the last service call).
The HistoricalServiceCall entity should have an ID, an embedded ServiceCall field (the data of the HistoricalServiceCall), and, potentially, a ManyToOne association to Customer if you want the OneToMany to be bidirectional.
The Customer should have a OneToMany association to HistoricalServiceCall.
I have a class A :
#Entity
public class A {
...
#ManyToMany
private Set<Ref> refs = new HashSet<Ref>();
...
}
And a class Ref :
#Entity
public class Ref {
// no link to A
}
I want to delete A and A_Ref but not Ref, but i get a org.hibernate.exception.ConstraintViolationException
Is there a simple way to do it or should i explicitely create a A_Ref class ?
Thanks
Edit :
I was attempting to delete my list of A directly in hql. I did it in object (broke the relations) and it worked (cascade + orphan deletion).
I am not sure, if I got you right, but from what I understood my first guess is, that you havent used all necessary annotations to make sure, that hibernate will automatically remove necessary references in Ref by itself.
Within a manyToMany relation, you can use joinTables and cascadetypes to make sure, that hibernate knows where to delete all necessary relations by itself without creating an own domainObject for it.
There are plenty of nice guides how to manage it. On first view the guide from mkyong looks pretty good. Note: He annotated getters instead of variable declarations! (which is just a question of taste).