Spring Data JPA - Many to many query - sql

I have two entities Person and Movie.
#Entity
public class Person {
..some fields
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "actors")
#OrderBy("id")
private Set<Movie> moviesActor = new TreeSet<>();
}
#Entity
public class Movie {
..fields
#JoinTable(name = "actor_movie",
joinColumns = { #JoinColumn(name = "movie_id") },
inverseJoinColumns = { #JoinColumn(name = "actor_id") })
private Set<Person> actors = new TreeSet<>();
}
There is many to many relationship so there is new table actor_movie to keep it. And how can I get every person that has any movie in its set? So what I want is to achieve is get every person that exists in actor_movie table. I tried used Spring data jpa but couldn't find right query.

Best Practices in entity relations:
Always use fetch = FetchType.LAZY.
When you want to fetch another side of the relation too, use JOIN FETCH Query.This resolves LazyInitializationException of hibernate also.
Always use spring.jpa.open-in-view=false
Example:
By Spring Data JPA with Hibernate as JPA Provider.
Entities:
public class Blog{
...
#ManyToMany(fetch = FetchType.LAZY) //default is LAZY in ManyToMany
#JoinTable(name="blog_tag",
joinColumns = #JoinColumn(name = "blog_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
#OrderBy //order by tag id
private Set<Tag> tags = new HashSet<>();
//2 utility methods in owner side
public void addTag(Tag tag){
tags.add(tag);
tag.getBlogs().add(this);
}
public void removeTag(Tag tag){
tags.remove(tag);
tag.getBlogs().remove(this);
}
//override equals & hashcode
}
public class Tag {
...
#ManyToMany(mappedBy = "tags")
private Set<Blog> blogs = new HashSet<>();
//override equals & hashcode
}
Now suppose, you want to fetch a Blog containing Tag items:
Repository:
#Repository
public interface BlogRepository extends JpaRepository<Blog, Long> {
#Query("select b from Blog b join fetch b.tags where b.name = :name")
Blog getBlog(#Param("name") String blogName);
}
service:
public interface BlogService {
Blog getBlog(String blogName);
}
#Service
public class BlogServiceImpl implements BlogService{
#Autowired
private BlogRepository blogRepository;
#Override
public Blog getBlog(String blogName) {
return blogRepository.getBlog(blogName);
}
}

You only need a single JOIN between Person and Movie. As Hibernate abstracts the existence of the middle table, you don't need to worry about it.
So, with Spring Data Repository:
class PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByMoviesActor();
}
With Jpql:
SELECT person FROM Person person JOIN person.moviesActor movie

Since you are using Fetch type lazy, you need to use join fetch to get moviesActor.
You can use jpql with spring data. I have not tested the queries below, but should work.
public interface PersonRepository extends JpaRepository<Person, Long> { //Long if Person.id is of type Long
#Query("SELECT p FROM Person p LEFT JOIN FETCH p.moviesActor WHERE size(p.moviesActor) > 0");
List<Person> findActors1();
// Or
#Query("SELECT p FROM Person p JOIN FETCH p.moviesActor");
List<Person> findActors2();
}
More about jpql size() operator here: https://www.thoughts-on-java.org/jpql/

You can use join directely :
#Query("SELECT p FROM Person p JOIN p.moviesActor movie");
List findPersonHasMovie();

Related

How to include additional data in one entity from other tables (via joins or subqueries) and still have it be insertable/updatable?

So I have the following code working correctly on my ecommerce site.
#Entity
#Table(name = "v_customer_wishlist")
#NamedQuery(name = "VCustomerWishlist.findAll", query = "SELECT w FROM VCustomerWishlist w")
public class VCustomerWishlist implements Serializable {
#Id
#Column(name = "cart_id")
private int _cartId;
//get/set methods
...
#OneToMany(mappedBy = "_wishlist", cascade = CascadeType.ALL)
private List<VCustomerWishlistItem> _items;
//get/set methods
}
#Entity
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(int cartId, int productId) {
VCustomerWishlistItemPK id = new VCustomerWishlistItemPK (cartId, productId);
setId(id);
}
#EmbeddedId
private VCustomerWishlistItemPK id; //is PK for cartId and productId
//get/set methods
...
#ManyToOne
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#Column(name = "product_name")
private String _productName;
//get/set methods
...
}
Then somewhere in my backing bean, I could do somethin like this (simplified version):
...
VCustomerWishlist wishlist = getCustomer().getWishlistById(cartId);
...
VCustomerWishlistItem item = new VCustomerWishlistItem(wishlist.getId(), product.getId());
...
item.setSequenceNum(wishlist.getItems().size()+1);
item.setProductName(product.getName());
item.setQuantity(1);
wishlist.addItem(item);
wishlistItemService.save(item);
...
So I can add items (product references) to wishlist and JPA will correctly generate the INSERT INTO queries and so forth.
However, upon thinking about it, I thought it would be better to retrieve this data directly from my 'master_products' table instead of what was stored in the VCustomerWishlistItem.
This way I would always have the most up-to-date productName, unitPrice and so forth for wishlist items saved weeks or months before.
The thing is, if I modify the view in my database to include this additional info by adding joins or subqueries; as soon as add joins or subqueries to my view, it becomes non-inserable/updatable.
I thought that it could be done via JPLQ in one #NamedQuery definition, but I understand these are designed to be used manually when retrieving desired sets. As opposed to the nice built-in way that JPA automatically retrieves the wishlist.items resolving it with the indicating annotation properties.
The fantasy property would be one where I can specify a direct table source for the entity, ignoring the join and subquery tables.
So for example, if the source for 'v_customer_wishlist_items_readonly' was:
SELECT
`cwi`.`cart_id` AS `cart_id`,
`cwi`.`product_id` AS `product_id`,
`cwi`.`sequence_num` AS `sequence_num`,
`mp_readonly`.`product_name` AS `product_name`,
`mp_readonly`.`product_web_id` AS `product_web_id`,
`mp_readonly`.`unit_price` AS `unit_price`,
`cwi`.`quantity` AS `quantity`,
`mp_readonly`.`unit_price`*`csci`.`quantity` AS `item_subtotal`,
`cwi`.`create_datetime` AS `create_datetime`,
`cwi`.`update_datetime` AS `update_datetime`
FROM
`customer_wishlist_items` `cwi` JOIN `master_products` `mp_readonly` ON `cwi`.`product_id` = `mp_readonly`.`product_id`
ORDER BY `cwi`.`sequence_num`;
It would be ideal to have a an annotation where I could indicate that primary table name is 'customer_wishlist_items', so all updates/inserts would only apply to this table and changes to the rest of the read-only fields would be ignored.
So somethint like this:
#Entity
#Table(name = "v_customer_wishlist_items_readonly")
#PrimaryTable(name = "customer_wishlist_items") //fantasy annotation
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT s FROM VCustomerWishlistItem s")
public class VCustomerWishlistItem implements Serializable {
...
Does anyone know what would be the correct way of implementing this?
Thanks a lot in advance!
Why not use derived ids or the MapsId to let JPA set your foreign key/id columns for you?
#Entity
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(VCustomerWishlist cart, Product product) {
this._wishList = cart;
this._product = product;
setId(new VCustomerWishlistItemPK());//JPA will populate this for you
}
#EmbeddedId
private VCustomerWishlistItemPK id; //is PK for cartId and productId
//get/set methods
...
#ManyToOne
#MapsId("cartId")
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#MapsId("productId")
#JoinColumn(name = "product_id")
private Product _product;
//get/set methods
...
}
With this, you don't need to have or lookup the cartId/productId values at all as JPA will figure them out and set them for you, allowing you do just use code like:
VCustomerWishlistItem item = new VCustomerWishlistItem(wishlist, product);
...
item.setSequenceNum(wishlist.getItems().size()+1);
item.setQuantity(1);
wishlist.addItem(item);
wishlistItemService.save(item);
You should probably just set the sequenceNum and add the item to the wishlist in the item constructor, though I'm not a fan this approach to sequencing as it can lead to concurrency issues and problems maintaining it.
You can also do away with the EmbeddedId if you don't need it within your entity and use it as a primary key class; you'd just have to change the property names within it to match the relationships names from the entity:
#Entity
#IdClass(VCustomerWishlistItemPK.class)
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(VCustomerWishlist cart, Product product) {
this._wishList = cart;
this._product = product;
}
...
#ManyToOne
#Id
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#Id
#JoinColumn(name = "product_id")
private Product _product;
//get/set methods
...
}
The primary key class might then look like:
public class VCustomerWishlistItemPK {
public Integer _product;
public Integer _wishlist;
//optional getter/setter methods..
}
The properties within the ID class must match the names of the properties in your entities, but use the type of the primary key from the referenced class.

(N + 1) selects problem with child Map-collection

friends! I have these entities:
Document:
#Entity
#Table(name = "documents")
public class Document extends AbstractNamedEntity {
....
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "document_change_notices",
joinColumns = #JoinColumn(name = "document_id"),
inverseJoinColumns = #JoinColumn(name = "change_notice_id"))
#MapKeyColumn(name = "change")
private Map<Integer, ChangeNotice> changeNotices;
....
}
and ChangeNotice:
#Entity
#Table(name="change_notices")
public class ChangeNotice extends AbstractNamedEntity {
....
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "document_change_notices", joinColumns = #JoinColumn(name = "change_notice_id"))
#MapKeyJoinColumn(name = "document_id")
#Column(name = "change")
private Map<Document, Integer> documents;
....
}
And these are my repositories:
For Document:
public interface DocumentRepository extends JpaRepository<Document, Integer> {
....
#Query("select d from Document d left join fetch d.changeNotices where d.decimalNumber=:decimalNumber")
Optional<Document> findByDecimalNumberWithChangeNotices(#Param("decimalNumber") String decimalNumber);
....
}
and for ChangeNotice:
public interface ChangeNoticeRepository extends JpaRepository<ChangeNotice, Integer> {
....
#Query("select c from ChangeNotice c left join fetch c.documents where c.id=:id")
Optional<ChangeNotice> findByIdWithDocuments(#Param("id") int id);
....
}
So, when i want to get Document with changeNotices, thats not a problem, i have only one select.
But when i want to get ChangeNotice with documents - i have (n + 1), first for document, and n for changeNotices Map.
I use join fetch in my query, but it does not help.
I think the problem is that in Document i have Map<K,V>, where entity is a value, and i should use #ManyToMany relationship. And in ChangeNotice i have Map<K,V>, where entity is a key, and i should use #ElementCollection.
Are there any ways to write a query that select ChangeNotice with documents in one select? (without changing my entities code, may be small fixes possible)
So, a lot of time has passed, i didn't find an answer. But it is my architecture issue. I had to use another class, that contains Document, ChangeNotice, and Integer field. My Document and ChangeNotice entities had child collection of this class with #OnetoMany relationship. It solves the problem.

JPA query to retrieve data using list of matching tuples as arguments [duplicate]

I have an Entity Class like this:
#Entity
#Table(name = "CUSTOMER")
class Customer{
#Id
#Column(name = "Id")
Long id;
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
How to write findBy method for the below query using crudrepository spring data jpa?
select * from customer where (email, mobile) IN (("a#b.c","8971"), ("e#f.g", "8888"))
I'm expecting something like
List<Customer> findByEmailMobileIn(List<Tuple> tuples);
I want to get the list of customers from given pairs
I think this can be done with org.springframework.data.jpa.domain.Specification. You can pass a list of your tuples and proceed them this way (don't care that Tuple is not an entity, but you need to define this class):
public class CustomerSpecification implements Specification<Customer> {
// names of the fields in your Customer entity
private static final String CONST_EMAIL_ID = "emailId";
private static final String CONST_MOBILE = "mobile";
private List<MyTuple> tuples;
public ClaimSpecification(List<MyTuple> tuples) {
this.tuples = tuples;
}
#Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// will be connected with logical OR
List<Predicate> predicates = new ArrayList<>();
tuples.forEach(tuple -> {
List<Predicate> innerPredicates = new ArrayList<>();
if (tuple.getEmail() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_EMAIL_ID), tuple.getEmail()));
}
if (tuple.getMobile() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_MOBILE), tuple.getMobile()));
}
// these predicates match a tuple, hence joined with AND
predicates.add(andTogether(innerPredicates, cb));
});
return orTogether(predicates, cb);
}
private Predicate orTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.or(predicates.toArray(new Predicate[0]));
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
}
Your repo is supposed to extend interface JpaSpecificationExecutor<Customer>.
Then construct a specification with a list of tuples and pass it to the method customerRepo.findAll(Specification<Customer>) - it returns a list of customers.
It is maybe cleaner using a projection :
#Entity
#Table(name = "CUSTOMER")
class CustomerQueryData {
#Id
#Column(name = "Id")
Long id;
#OneToOne
#JoinColumns(#JoinColumn(name = "emailId"), #JoinColumn(name = "mobile"))
Contact contact;
}
The Contact Entity :
#Entity
#Table(name = "CUSTOMER")
class Contact{
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
After specifying the entities, the repo :
CustomerJpaProjection extends Repository<CustomerQueryData, Long>, QueryDslPredicateExecutor<CustomerQueryData> {
#Override
List<CustomerQueryData> findAll(Predicate predicate);
}
And the repo call :
ArrayList<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("a#b.c","8971"));
contacts.add(new Contact("e#f.g", "8888"));
customerJpaProjection.findAll(QCustomerQueryData.customerQueryData.contact.in(contacts));
Not tested code.

Spring Data Rest Left Outer Join non-Entity POJO Null Entity Error

How do i accomplish getting the this interface method to work? i am using a MySQL DB if that matters...
public interface PersonRoleRepository extends CrudRepository<PersonRole,Long>{
//This causes null entity error from hibernate even though the SQL works outside hibernate
#Query(value="select * from Role r left outer join Person_Role pr on r.id = pr.role_id and pr.person_id = ? order by pr.expires_date desc", nativeQuery = true)
List<PersonRoleDto> getAllRolesAndPersonsStatusWithEachRole(int personId);
}
Here is the SQL query that returns what i want in SQL Workbench...
Select r.*, pr.*
from
Role r
left outer join person_role pr on r.id = pr.role_id and pr.person_id = ?
order by pr.expires_date desc;
Important MySQL database structure...
Table: Role
role_id bigint
name varchar
description varchar
...
Table: Person
person_id bigint
first_name varchar
last_name varchar
...
Table Person_Role_Link
person_role_id bigint
role_id bigint
person_id bigint
...
alter table person_Role_Link add constraint l1 foreign key (person_id) references Person(person_id)
alter table person_Role_Link add constraint l2 foreign key (role_id) references Role(role_id)
Here is the entity info...
#Entity
#Table(name="Role")
#EntityListeners(AuditEntityListener.class)
public class Role extends AbstractAuditEntity {
private static final long serialVersionUID= 44543543543543454543543L;
#id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="role_id")
private long id;
#NotNull
private String fullName
...
#OneToMany(mappedBy="role",cascade=CascadeType.ALL)
private Set<PersonRole> personRoles = new HashSet<>();
...
}
#Entity
#Table(name="Person_Role_Link")
#EntityListeners(AuditEntityListener.class)
class PersonRole extends AbstractAuditEntry{
private static final long serialVersion = 54324243242432423L;
#id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="person_role_id")
private long id;
#ManyToOne
#JoinColumn(name="person_id")
private Person person;
#ManyToOne
#JoinColumn(name="role_id")
private Role role;
...
}
#Entity
#Table(name="Person")
#EntityListeners(AuditEntityListener.class)
public class Person extends AbstractAuditEntity{
private ... serialVersionUID...;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="person_id")
private Long id;
...
#OneToMany(mappedBy="person", cascade=CascadeType.ALL)
private Set<PersonRole> personRoles = new HashSet<>();
...
}
Just for now i made a simple interface...
public interface PersonRoleDto {
String getFullName();
String getDescription();
//i want more attributes but for now i will just see if it works with the basics
}
Here is the latest HQL i tried...
public interface PersonRoleRepository extends CrudRepository<PersonRole,Long>{
//PersistentEntity must not be null
#Query("select r.fullName as fullName, r.description as description from Role r left join r.personRoles pr where pr.person = :person")
List<PersonRoleDto> findAllRolesWithPersonsStatus(#Param("person") Person person);
...
}
Whether I use HQL or native SQL i get a null entity error from hibernate. Also, the SQL generated from the HQL works without error but i still get a null entity error in code and, second, the SQL generated from HQL is slightly off which makes the results off. That's why i was trying so hard to get the native SQL to work.
The relationship is used to figure out how many people are in a role and at other times what roles a person has. This is a circular relationship, i'd say. I work on an Intranet so i had to hand type everything. If there are any problems seen in my code other than with the stated native query as stated then it is most likely because i had to hand type everything and not because the code is buggy. Everything else works so far but this one thing.
All help is appreciated.
UPDATE!!!!
I think this is the answer to my problem but when i try it i still get the error: PersistentEntity must not be null!
Here is how i tried to set it up...
//Added this to the top of PersonRole entity
#SqlResultSetMapping(
name="allRolesAndPersonsStatusWithEachRole"
classes={
#ConstructorResult(
targetClass=PersonRoleStatus.class,
columns={
#ColumnResult(name="full_name"),
#ColumnResult(name="description"),
...
}
)
}
)
#NamedNativeQuery(name="PersonRole.getAllRolesAndPersonsStatusWithEachRole",
query="Select r.*, pr.* from Role r Left Join person_role_link on r.role_id = pr.role_id and pr.person_id = :id", resultSetMapping="allRolesAndPersonsStatusWithEachRole")
Created my DTO like this...
public class RolePersonStatus {
private String fullName;
private String description;
private String ...
public RolePersonStatus(String fullName, String description, ...){
this.fullName = fullName;
this.description = description;
...
}
}
In my repository i just have:
//No annotation because it stated that the name of the method just needed to match the native query name?!?!?
List<RolePersonStatus> findAllRolesWithPersonStatus(#Param("id" Long id);
What am i missing???????
Try this way:
Entities
#Entity
#Table(name = "parents")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany
private List<Child> children = new ArrayList<>();
//...
}
#Entity
#Table(name = "children")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne(optional = false)
private Reference reference;
#OneToMany
private final List<Toy> toys = new ArrayList<>();
//...
}
#Entity
#Table(name = "references")
public class Reference {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String description;
//...
}
#Entity
#Table(name = "toys")
public class Toy {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
//...
}
DTO
public interface DemoDto {
String getParentName();
String getChildName();
String getToyName();
String getDescription();
}
Repository
public interface ParentRepo extends JpaRepository<Parent, Long> {
#Query("select " +
"p.name as parentName, " +
"c.name as childName, " +
"t.name as toyName, " +
"r.description as description " +
"from " +
"Parent p " +
"join p.children c " +
"join c.reference r " +
"join c.toys t " +
"where c.id = ?1 " +
"order by r.description desc")
List<DemoDto> getDto(Long childId);
}
Usage
#RunWith(SpringRunner.class)
#SpringBootTest
public class ParentRepoTest {
#Autowired
private ParentRepo parentRepo;
#Test
public void getDto() throws Exception {
List<DemoDto> dtos = parentRepo.getDto(3L);
dtos.forEach(System.out::println);
}
}
Result
{parentName=parent2, toyName=Toy7, childName=child3, description=Description1}
{parentName=parent2, toyName=Toy8, childName=child3, description=Description1}
{parentName=parent2, toyName=Toy9, childName=child3, description=Description1}
More info is here.
Working example.

Hibernate - one table multiple entities

Firstly, I have read Hibernate - One table with multiple entities?.
However, I would like to map two entities to the same table, but I would like both of them to be entities, which I can select from. What I mean:
One table: Person (id, name, dateOfBirth, city, street, zipcode).
Two Entities: Person (id, name, dateOfBirth), Address (id, city,
street, zipcode).
So it's a 1:1 relationship between Entities, but still 1 table in DB.
If I do it using the proposed solution (component keyword) in the above link, I can't query Address directly (I can access it via Person entity). And I want to be able to do
session.createCriteria(Adres.class)
How do I do that?
UPDATE:
I tried the one-to-one association between entities, in Address mapping:
<one-to-one name="Person " class="model_mapowanie_xml.Person "/>
and in Person mapping:
<one-to-one name="Address" class="model_mapowanie_xml.Address "/>
Both classes have fields referring to the other one. Selecting records works fine for that. However, how can I add in one transaction a record using both entities? (Id is db-generated)
Address ad = new Address();
ad.setProperty("Sydney");
Person p = new Person();
p.setProperty("John");
p.setAddress(ad);
session.save(p);
and only Person part is saved, the address property remains empty.
This is very simple to achieve with JPA and Hibernate.
Let's assume you are using the following book database table:
Mapping entities
Now, you can map two entities: Book and BookSummary to this table.
First, we will create a BaseBook abstract class which will be extended by all entities:
#MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
#Id
#GeneratedValue
private Long id;
#NaturalId
#Column(length = 15)
private String isbn;
#Column(length = 50)
private String title;
#Column(length = 50)
private String author;
public Long getId() {
return id;
}
public T setId(Long id) {
this.id = id;
return (T) this;
}
public String getIsbn() {
return isbn;
}
public T setIsbn(String isbn) {
this.isbn = isbn;
return (T) this;
}
public String getTitle() {
return title;
}
public T setTitle(String title) {
this.title = title;
return (T) this;
}
public String getAuthor() {
return author;
}
public T setAuthor(String author) {
this.author = author;
return (T) this;
}
}
Now, the BookSummary entity simply extends the BaseBook superclass and adds no additional entity attribute.
#Entity(name = "BookSummary")
#Table(name = "book")
public class BookSummary extends BaseBook<BookSummary> {
}
On the other hand, the Book entity extends the BaseBook superclass and maps the properties attribute.
#Entity(name = "Book")
#Table(name = "book")
#TypeDef(
name = "jsonb",
typeClass = JsonBinaryType.class
)
#DynamicUpdate
public class Book extends BaseBook<Book> {
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private String properties;
public String getProperties() {
return properties;
}
public Book setProperties(String properties) {
this.properties = properties;
return this;
}
public ObjectNode getJsonProperties() {
return (ObjectNode) JacksonUtil
.toJsonNode(properties);
}
}
Persisting entities
This way, you can persist either a Book entity:
entityManager.persist(
new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea")
.setProperties(
"{" +
" \"publisher\": \"Amazon\"," +
" \"price\": 44.99," +
" \"publication_date\": \"2016-20-12\"," +
" \"dimensions\": \"8.5 x 1.1 x 11 inches\"," +
" \"weight\": \"2.5 pounds\"," +
" \"average_review\": \"4.7 out of 5 stars\"," +
" \"url\": \"https://amzn.com/973022823X\"" +
"}"
)
);
or a BookSummary:
entityManager.persist(
new BookSummary()
.setIsbn("978-1934356555")
.setTitle("SQL Antipatterns")
.setAuthor("Bill Karwin")
);
Fetching entities
You can fetch the BookSummary entity:
BookSummary bookSummary = entityManager
.unwrap(Session.class)
.bySimpleNaturalId(BookSummary.class)
.load("978-9730228236");
assertEquals(
"High-Performance Java Persistence",
bookSummary.getTitle()
);
or the Book entity if you want:
Book book = entityManager
.unwrap(Session.class)
.bySimpleNaturalId(Book.class)
.load("978-9730228236");
assertEquals(
"High-Performance Java Persistence, 2nd edition",
book.getTitle()
);
Conclusion
So mapping multiple entities to the same database table, not only that it allows us to fetch data more efficiently, but it also speeds up the dirty checking process as Hibernate has to inspect fewer entity properties.
The only drawback of using this approach is that you have to make sure you don’t fetch more than one entity type for the same database table record, as otherwise, this can cause inconsistencies when flushing the Persistence Context.
You should be able to do it using #Table annotation. These entites will be treated as different entites but will be mapped onto same table.
#Entity
#Table(name="PERSON_TABLE")
class Person {}
#Entity
#Table(name"PERSON_TABLE")
class Address {}
Edit:
If you want to save both entities in one transaction you either have to explicitly save them using Session or set cascade property to cascade operations on relationship. I guess you want to cascade operations on Address when you do something on Person. See CascadeType if you use annotations.
In your hbm it would look like
<one-to-one name="Person" class="model_mapowanie_xml.Person" cascade="all"/>