Many to Many relationships using Spring Boot, Jackson and Hibernate - java

I'm working on a rest project using Spring Boot and Hibernate and am currently trying to figure out how to handle my json-serialization.
The schema shown in the ERD above is mapped by Hibernate and works fine.
The problem arises when I make a get request to a controller. My understanding is that Spring now tries to serialize the object-chain using Jackson. Because both the parent and child objects have one another as an attribute, we find ourselves hitting an infinite recursion loop.
Now I've looked into #JsonIgnore, #JsonView, #JsonManagedReference and #JsonBackReference but these only seem to work for one-to-many relationships.
What I'm looking for is a situation where when I for instance make a GET request to /users/{id}, I get the user object including all it's relationship attributes (let's call it the full object), but the relationship attributes themselves don't show their relationship-attributes (minimized objects). This works fine with the annotations mentioned above, but how do I make this work the other way as well?
Desired response for: /users/{id}
{ // full user object
id: 1,
username: 'foo',
// password can be JsonIgnored because of obvious reasons
role: { // minimized role object
id: 1,
name: 'bar'
// NO USERS LIST
}
area: { //minimized area object
id: 2,
name: 'some val'
// NO USERS LIST
// NO TABLES LIST
}
}
Desired response for /userrole/{id}
{ // full role object
id: 1,
name: 'waiter'
users: [
{ // minmized user object
id: 1,
username: 'foo'
// password can be JsonIgnored because of obvious reasons
// NO ROLE OBJECT
// NO AREA OBJECT
},
{ // minmized user object
id: 1,
username: 'foo'
// password can be JsonIgnored because of obvious reasons
// NO ROLE OBJECT
// NO AREA OBJECT
}
]
}
In general: I'd like a full object when the request is made to the entity directly and a minimized object when requested indirectly.
Any Ideas? I hope my explanation is clear enough.
UPDATE
The Area, User and UserRole POJO's as requested in the comment sections.
User
#Entity
#Table(name = "users", schema = "public", catalog = "PocketOrder")
public class User {
private int id;
private String username;
private String psswrd;
private List<Area> areas;
private UserRole Role;
#Id
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "username", nullable = false, length = 20)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Basic
#JsonIgnore
#Column(name = "psswrd", nullable = true, length = 40)
public String getPsswrd() {
return psswrd;
}
public void setPsswrd(String psswrd) {
this.psswrd = psswrd;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (id != user.id) return false;
if (username != null ? !username.equals(user.username) : user.username != null) return false;
if (psswrd != null ? !psswrd.equals(user.psswrd) : user.psswrd != null) return false;
return true;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (psswrd != null ? psswrd.hashCode() : 0);
return result;
}
#ManyToMany(mappedBy = "users")
public List<Area> getAreas() {
return areas;
}
public void setAreas(List<Area> areas) {
this.areas = areas;
}
#ManyToOne
#JoinColumn(name = "role_fk", referencedColumnName = "id", nullable = false)
public UserRole getRole() {
return Role;
}
public void setRole(UserRole role) {
Role = role;
}
}
UserRole
#Entity
#javax.persistence.Table(name = "userroles", schema = "public", catalog = "PocketOrder")
public class UserRole {
private int id;
private String name;
private List<User> users;
#Id
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 20)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRole userRole = (UserRole) o;
if (id != userRole.id) return false;
if (name != null ? !name.equals(userRole.name) : userRole.name != null) return false;
return true;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
#OneToMany(mappedBy = "role")
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
users = users;
}
}
Area
#Entity
#javax.persistence.Table(name = "areas", schema = "public", catalog = "PocketOrder")
public class Area {
private int id;
private String name;
private List<User> users;
private List<Table> tables;
#Id
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Basic
#Column(name = "name", nullable = false, length = 20)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Area area = (Area) o;
if (id != area.id) return false;
if (name != null ? !name.equals(area.name) : area.name != null) return false;
return true;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
#ManyToMany
#JoinTable(name = "areas_users", catalog = "PocketOrder", schema = "public", joinColumns = #JoinColumn(name = "area_fk", referencedColumnName = "id", nullable = false), inverseJoinColumns = #JoinColumn(name = "user_fk", referencedColumnName = "id", nullable = false))
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
#OneToMany(mappedBy = "area")
public List<Table> getTables() {
return tables;
}
public void setTables(List<Table> tables) {
this.tables = tables;
}
}

Try use #JsonSerialize on specific points:
For sample:
1 - Map your field
#JsonSerialize(using = ExampleSampleSerializer.class)
#ManyToOne
private Example example;
2 - Create custom jackson serializer (Here you can control the serialization)
public class ExampleSampleSerializer extends JsonSerializer<Example> {
#Override
public void serialize(Example value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("first");
jsonGenerator.writeNumber(value.getFirstValue());
jsonGenerator.writeFieldName("second");
jsonGenerator.writeNumber(value.getSecondValue());
jsonGenerator.writeFieldName("third");
jsonGenerator.writeNumber(value.getAnyAnotherClass().getThirdValue());
jsonGenerator.writeEndObject();
}
}

The way I worked around this on a Many-to-Many relationship is by using
#JsonIgnore
On one of the Entities.
For example we have Person and Child entities. One Person can have many children and vice-versa.
On Person we have :
public class Person
{
//Other fields ommited
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#JoinTable(name = "person_child",
joinColumns = {
#JoinColumn(name = "person_id", referencedColumnName = "id", nullable = false,
updatable = false)
},
inverseJoinColumns = {
#JoinColumn(name = "child_id", referencedColumnName = "id", nullable =
false, updatable = false)
})
private Set<Child> children = new HashSet<>() ;
}
And on Child we have :
public class Child
{
#JsonIgnore
#ManyToMany(mappedBy = "children", fetch = FetchType.LAZY)
private Set<Person> people = new HashSet<>() ;
}
Now when we get a Person, we also get all his connected children. But when we get a Child then we don't get all People because we have #JsonIgnore annotation on it.
This fixes the Infinite Recursion problem, and raises this one.
My workaround was by writing a query to get me all the People connected to a specific child_id.
Below you may see my code:
public interface PersonDAO extends JpaRepository<Person, Long>
{
#Query(value = "SELECT * " +
" FROM person p INNER JOIN person_child j " +
"ON p.id = j.person_id WHERE j.child_id = ?1 ", nativeQuery = true)
public List<Person> getPeopleViaChildId(long id);
}
And i use it whenever I want to get all People from a child.

Related

Jpa + Spring Data : cascading of a collection with compound key

I'm currently working on a small shop application for my School.
I have 2 objects I want to save :
Order.java
#Entity
#Table(name = "ORDERS")
public class Order {
private Integer id;
private Date orderDate;
private MailingAddress mailingAddress;
private User user;
private Collection<OrderLine> orderLines;
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "ORDER_DATE")
public Date getOrderDate() {
return orderDate;
}
public void setOrderDate(Date orderDate) {
this.orderDate = orderDate;
}
#ManyToOne
#JoinColumn(name = "SHIPPING_ADR_ID", referencedColumnName = "ID")
public MailingAddress getMailingAddress() {
return mailingAddress;
}
public void setMailingAddress(MailingAddress mailingAddressByShippingAdrId) {
this.mailingAddress = mailingAddressByShippingAdrId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID", referencedColumnName = "LOGIN")
public User getUser() {
return user;
}
public void setUser(User userByUserId) {
this.user = userByUserId;
}
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Collection<OrderLine> getOrderLines() {
return orderLines;
}
public void setOrderLines(Collection<OrderLine> orderLinesesById) {
this.orderLines = orderLinesesById;
}
}
OrderLine.java
#Entity
#Table(name = "ORDER_LINES", schema = "")
#IdClass(OrderLinesPK.class)
public class OrderLine {
private int quantity;
private Integer orderId;
private String bookId;
private Book book;
private Order order;
#Basic
#Column(name = "QUANTITY")
public int getQuantity() {
return quantity;
}
public void setQuantity(int quantity) {
this.quantity = quantity;
}
#Id
#Column(name = "ORDERS_ID", insertable = false, updatable = false)
public Integer getOrderId() {
return orderId;
}
public void setOrderId(Integer ordersId) {
this.orderId = ordersId;
}
#Id
#Column(name = "BOOKS_ID", insertable = false, updatable = false)
public String getBookId() {
return bookId;
}
public void setBookId(String booksId) {
this.bookId = booksId;
}
#ManyToOne
#JoinColumn(name = "BOOKS_ID", referencedColumnName = "ISBN13", nullable = false, insertable = false, updatable = false)
public Book getBook() {
return book;
}
public void setBook(Book booksByBookId) {
this.book = booksByBookId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ORDERS_ID", referencedColumnName = "ID", nullable = false, insertable = false, updatable = false)
public Order getOrder() {
return order;
}
public void setOrder(Order ordersByOrderId) {
this.order = ordersByOrderId;
}
}
OrderLinesPK.java
public class OrderLinesPK implements Serializable {
private int ordersId;
private String booksId;
#Column(name = "ORDERS_ID")
#Id
public int getOrderId() {
return ordersId;
}
public void setOrderId(int ordersId) {
this.ordersId = ordersId;
}
#Column(name = "BOOKS_ID")
#Id
public String getBookId() {
return booksId;
}
public void setBookId(String booksId) {
this.booksId = booksId;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OrderLinesPK that = (OrderLinesPK) o;
if (ordersId != that.ordersId) return false;
if (booksId != null ? !booksId.equals(that.booksId) : that.booksId != null) return false;
return true;
}
#Override
public int hashCode() {
int result = ordersId;
result = 31 * result + (booksId != null ? booksId.hashCode() : 0);
return result;
}
}
An order contains a collection of order lines.
I'm trying to save the order + the order lines in one call to OrderRepository.
But when I do that, I get the error
org.hibernate.PropertyAccessException: Null value was assigned to a property of primitive type setter of edu.flst.bookstore.domaine.bo.OrderLinesPK.orderId
which is pretty logic (I know the Id of the order is unknow at this stage, because the primary key of order is auto-incremented (I use MySQL)).
I don't know how to make this work with one call to orderService (without saving orderLines with orderLinesRepository first). Is it even possible ?
Regards
An Order can contain many Books and a Book can appear in many Order(s). So the many-to-many relation is your OrderLine object essentially. I would set an id (autogenerated) and two many-to-one relations in OrderLine. Then you discard the OrderLinesPK class, you save the Order, Book and OrderLine objects in this order in the same transaction.In this way, your model is simpler and you only need to save an extra id (with no physical meaning) in the database (the id of the OrderLine object)

Joins on composite key using hibernate criteria

I am not getting how to write Hibernate criteria query to achieve the result similar to the result obtained by below SQL query. Please suggest me what are all steps need to be followed to achieve the result.
SELECT PRODUCT.PRODUCTNAME, ITEM.ITEMNAME
FROM PRODUCT_ITEM
JOIN PRODUCT
ON PRODUCT_ITEM.ID = PRODUCT.ID
JOIN ITEM
ON PRODUCT_ITEM.ID = ITEM.ID
Above is my Sql Query to fetch the product_name and item_name. It is working correctly.
I tried get the same result using HIBERNATE CRITERIA QUERY.
Criteria criteria = session.createCriteria(ProductItem.class,"pi");
criteria.createAlias("pi.pk.product", "pip");
criteria.createAlias("pi.pk.item", "pii");
criteria.setProjection(Projections.projectionList().add(Projections.property("pip.id")).add(Projections.property("pii.id")));
List<Object[]> list = criteria.list();
i am getting error saying
Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not execute query
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:67)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.loader.Loader.doList(Loader.java:2147)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2028)
at org.hibernate.loader.Loader.list(Loader.java:2023)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:95)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1569)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:283)
at checkComposite.main(checkComposite.java:38)
Caused by: org.postgresql.util.PSQLException: ERROR: missing FROM-clause entry for table "pip1_"
Position: 8
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2198)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1927)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255)
at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:561)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:419)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeQuery(AbstractJdbc2Statement.java:304)
at org.hibernate.jdbc.AbstractBatcher.getResultSet(AbstractBatcher.java:186)
at org.hibernate.loader.Loader.getResultSet(Loader.java:1668)
at org.hibernate.loader.Loader.doQuery(Loader.java:662)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:224)
at org.hibernate.loader.Loader.doList(Loader.java:2144)
Here my ENTITYS are as below.
#Entity
#Table(name = "item")
public class Item {
private Integer id;
private String name;
private List<ProductItem> productItems = new LinkedList<ProductItem>();
public Item() {
}
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
#Column(name = "item_id", nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "name")
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.item")
public List<ProductItem> getProductItems() {
return this.productItems;
}
public void setProductItems(List<ProductItem> productItems) {
this.productItems = productItems;
}
}
Product Entity
#Entity
#Table(name = "product")
public class Product {
private Integer id;
private String name;
private List<ProductItem> productItems = new LinkedList<ProductItem>();
public Product() {
}
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
#Column(name = "product_id", nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#Column(name = "name")
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.product")
public List<ProductItem> getProductItems() {
return this.productItems;
}
public void setProductItems(List<ProductItem> productItems) {
this.productItems = productItems;
}
}
PRODUCT_ITEM entity.
#Entity
#Table(name = "product_item")
#AssociationOverrides({
#AssociationOverride(name = "pk.item", joinColumns = #JoinColumn(name = "item_id")),
#AssociationOverride(name = "pk.product", joinColumns = #JoinColumn(name = "product_id"))
})
public class ProductItem {
private ProductItemPk pk = new ProductItemPk();
#EmbeddedId
private ProductItemPk getPk() {
return pk;
}
private void setPk(ProductItemPk pk) {
this.pk = pk;
}
#Transient
public Item getItem() {
return getPk().getItem();
}
public void setItem(Item item) {
getPk().setItem(item);
}
#Transient
public Product getProduct() {
return getPk().getProduct();
}
public void setProduct(Product product) {
getPk().setProduct(product);
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductItem that = (ProductItem) o;
if (getPk() != null ? !getPk().equals(that.getPk()) : that.getPk() != null) return false;
return true;
}
public int hashCode() {
return (getPk() != null ? getPk().hashCode() : 0);
}
}
Embedable Class is as below.
#Embeddable
public class ProductItemPk implements Serializable {
private Item item;
private Product product;
#ManyToOne
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
#ManyToOne
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductItemPk that = (ProductItemPk) o;
if (item != null ? !item.equals(that.item) : that.item != null) return false;
if (product != null ? !product.equals(that.product) : that.product != null)
return false;
return true;
}
public int hashCode() {
int result;
result = (item != null ? item.hashCode() : 0);
result = 31 * result + (product != null ? product.hashCode() : 0);
return result;
}
}
Try changing the query to:
Criteria criteria = session.createCriteria(ProductItem.class,"pi");
criteria.createAlias("pi.pk", "pipk");
criteria.createAlias("pipk.product", "pip");
criteria.createAlias("pipk.item", "pii");
criteria.setProjection(Projections.projectionList().add(Projections.property("pip.id")).add(Projections.property("pii.id")));
List<Object[]> list = criteria.list();

Foreign Key Problems with Hibernate (Bug?)

at our current project we are experiencing some difficulties. I recently changed some Hibernate Beans (our Article Bean and some underlying stuff) and I ran some tests and everything looked fine. Now my teammate is having exceptions with this message:
Foreign key (FK_09fd525ae6654c059394d22cc15:ARTBILDER [artikel_fk,servdat_fk])) must have same number of columns as the referenced primary key (ARTIKEL [AUTOIN_FIX])
The annotations are definitely correct. I had the same problem and decided to setup the project on my computer from scratch and the problems were gone. What can be the reason for these problems?
We are working on a legacy database and are only mapping our objects to the database and not generating the database with hibernate. And we are using HibernateSearch for full-text search (maybe this is related, because the first time this occured was after I added the HibernateSearch Annotations).
We are using a Firebird 2.5 instance.
EDIT:
here is the property the error is coming from:
The ID Class:
#Embeddable
public class ID implements Serializable {
private static final long serialVersionUID = 1810044174631580834L;
private Long autoin;
private Integer serverId;
public ID() {
}
public ID(Long autoin, Integer serverId) {
this.autoin = autoin;
this.serverId = serverId;
}
#Column(name = "autoin_fix")
public Long getAutoin() {
return this.autoin;
}
#Column(name = "servdat_fk")
public Integer getServerId() {
return this.serverId;
}
public void setAutoin(Long autoin) {
this.autoin = autoin;
}
public void setServerId(Integer serverId) {
this.serverId = serverId;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((this.autoin == null) ? 0 : this.autoin.hashCode());
result = prime * result
+ ((this.serverId == null) ? 0 : this.serverId.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
ID other = (ID) obj;
if (this.autoin == null) {
if (other.autoin != null) {
return false;
}
} else if (!this.autoin.equals(other.autoin)) {
return false;
}
if (this.serverId == null) {
if (other.serverId != null) {
return false;
}
} else if (!this.serverId.equals(other.serverId)) {
return false;
}
return true;
}
#Override
public String toString() {
return new StringBuilder().append("ID [").append(this.autoin)
.append("_").append(this.serverId).append("]").toString();
}
}
The Article class:
#Indexed
#Entity
#Table(name = "ARTIKEL")
public class Article {
private ID id;
private List<Picture> pictures;
...
#DocumentId
#EmbeddedId
#FieldBridge(impl = IDBridge.class)
public ID getId() {
return id;
}
#OneToMany
#JoinColumns({
#JoinColumn(name = "artikel_fk", referencedColumnName = "autoin_fix"),
#JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk") })
#IndexedEmbedded
public List<Picture> getPictures() {
return pictures;
}
}
The Picture class:
#Entity
#Table(name = "ARTBILDER")
public class Picture extends BasePicture {
...
protected ID id;
#EmbeddedId
#FieldBridge(impl = IDBridge.class)
#Field(store = Store.YES, index = Index.YES)
public ID getId() {
return id;
}
...
}
EDIT2: I may have a clue where this comes from, please standby.
EDIT3: Nope, not the error.
EDIT4: Here is the DDL:
CREATE TABLE ARTIKEL
(
AUTOIN_FIX NUM10_0 DEFAULT 0,
SERVDAT_FK NUM10_0 DEFAULT 0,
...
PRIMARY KEY (AUTOIN_FIX,SERVDAT_FK)
);
CREATE TABLE ARTBILDER
(
AUTOIN_FIX NUM10_0 DEFAULT 0,
ARTIKEL_FK NUM10_0 DEFAULT 0,
SERVDAT_FK NUM10_0 DEFAULT 0,
...
PRIMARY KEY (AUTOIN_FIX,SERVDAT_FK)
);
Here is full link and description
OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "DATA_VALUE", joinColumns = {
#JoinColumn(name = "DATA_ID"),
}, inverseJoinColumns = {
#JoinColumn(name = "COLUMN_NM")
})
List<DataValue> dataValueList;
OR more Descriptive
#Entity
public class Parent implements Serializable {
#Id
public ParentPk id;
public int age;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumns ({
#JoinColumn(name="parentCivility", referencedColumnName = "isMale"),
#JoinColumn(name="parentLastName", referencedColumnName = "lastName"),
#JoinColumn(name="parentFirstName", referencedColumnName = "firstName")
})
public Set<Child> children; //unidirectional
...
}

Issue with cascade persisting of the composite primary key mapping based on UUID identity

So here is short description of my problem. I have two tables, first contains Person details:
#Entity
#Converter(name = "uuidConverter", converterClass = UUIDConverter.class)
public class Person {
private UUID mId;
private String mLogin;
#Id
#UuidGenerator(name = "uuid")
#GeneratedValue(generator = "uuid")
#Column(name = "id", nullable = false)
#Convert("uuidConverter")
public UUID getId() {
return mId;
}
#Column(name = "login", nullable = false)
public String getLogin() {
return mLogin;
}
public Person setId(UUID id) {
mId = id;
return this;
}
public Person setLogin(String login) {
mLogin = login;
return this;
}
}
Second table contains multiple person preferences:
#IdClass(PersonPreference.Pk.class)
#Table(name = "person_preference")
#Entity
#Converter(name = "uuidConverter", converterClass = UUIDConverter.class)
public class PersonPreference {
private Person mPerson;
private String mComponentUid;
private String mComponentProperties;
#SuppressWarnings("UnusedDeclaration")
static class Pk implements Serializable {
private String mComponentUid;
private UUID mPerson;
public String getComponentUid() {
return mComponentUid;
}
public void setComponentUid(String componentUid) {
mComponentUid = componentUid;
}
#Convert("uuidConverter")
public UUID getPerson() {
return mPerson;
}
public void setPerson(UUID person) {
mPerson = person;
}
#Override
public int hashCode() {
return Objects.hashCode(mComponentUid, mPerson);
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Pk other = (Pk) obj;
return Objects.equal(this.mComponentUid, other.mComponentUid) && Objects.equal(this.mPerson, other.mPerson);
}
}
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "person_id", nullable = false)
public Person getPerson() {
return mPerson;
}
#Id
#Column(name = "component_uid", nullable = false)
public String getComponentUid() {
return mComponentUid;
}
#Column(name = "component_properties", nullable = false)
public String getComponentProperties() {
return mComponentProperties;
}
public PersonPreference setPerson(Person person) {
mPerson = person;
return this;
}
public PersonPreference setComponentUid(String componentUid) {
mComponentUid = componentUid;
return this;
}
public PersonPreference setComponentProperties(String componentProperties) {
mComponentProperties = componentProperties;
return this;
}
#Override
public int hashCode() {
return Objects.hashCode(mPerson, mComponentUid, mComponentProperties);
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PersonPreference other = (PersonPreference) obj;
return Objects.equal(this.mPerson, other.mPerson)
&& Objects.equal(this.mComponentUid, other.mComponentUid)
&& Objects.equal(this.mComponentProperties, other.mComponentProperties);
}
}
I also wrote a simple test:
Person person = new Person()
.setLogin("PersonPreferencePersistenceTestLogin");
PersonPreference personPreference = new PersonPreference()
.setPerson(person)
.setComponentUid("4028808C3AA49ABB013AA49ABB2B0000")
.setComponentProperties("{123}");
mPersonPreferenceService.save(personPreference);
Optional<PersonPreference> newPersonPreference = mPersonPreferenceService.getByPersonAndComponentUid(
person,
"4028808C3AA49ABB013AA49ABB2B0000"
);
Assert.assertEquals(personPreference.getComponentProperties(), newPersonPreference.get().getComponentProperties());
We used previously Hibernate as JPA provider and that code works perfectly. Unfortunately, after switching JPA provider to Eclipselink, cascade persisting dosen't work correctly. From log I found out:
--INSERT INTO PERSON (id, login) VALUES (?, ?)
bind => [f2ce518c-8f37-4fac-bf5b-c8225d228b28, PersonPreferencePersistenceTestLogin]
--INSERT INTO person_preference (component_uid, component_properties, person_id) VALUES (?, ?, ?)
bind => [4028808C3AA49ABB013AA49ABB2B0000, {123}, f2ce518c-8f37-4fac-bf5b-c8225d228b28]
--SELECT component_uid, component_properties, person_id FROM person_preference WHERE ((person_id = ?) AND (component_uid = ?))
bind => [null, 4028808C3AA49ABB013AA49ABB2B0000]
But person.getId() returns null value. I don't know why? Other mappings works fine with cascade persisting, but that fails in Eclipselink.

Hibernate self join many to many with extra column

Help me to understand how I can realize many to many self join with extra column using hibernate? Should I map join table too? Or may be exists another way? I can not find anything usefull for me...
Map the join table as a dedicated entity and then link it via two OneToMany relationships. This often is the right way as it becomes more than just a technical detail as soon as you add more columns.
This should work the same way for a self join, you just have two fields on that model that are associated to the joining entity.
See this answer, where this is described in more detail.
I've been struggling with this question for a long time, perhaps someone will find Wolfram's wording difficult, so I'll explain it in more detail:
Imagine, what we need map something like that uml_diagramm
I needed to set up links between any two users, and the links were used to chat between them. So our user class will be :
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
#Entity
public class User implements Serializable {
private String email;
private String password;
private Long id;
private String nickname;
private String phone;
private Collection<Chats> chatsById;
private Collection<Chats> chatsById_0;
#Basic
#Column(name = "email", nullable = false, length = -1)
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#Basic
#Column(name = "password", nullable = false, length = -1)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Id
#Column(name = "id", nullable = false)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Basic
#Column(name = "nickname", nullable = true, length = -1)
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
#Basic
#Column(name = "phone", nullable = true, length = -1)
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(email, user.email) && Objects.equals(password, user.password) && Objects.equals(id, user.id) && Objects.equals(nickname, user.nickname) && Objects.equals(phone, user.phone);
}
#Override
public int hashCode() {
return Objects.hash(email, password, id, nickname, phone);
}
#OneToMany(mappedBy = "userByIdFrom")
public Collection<Chats> getChatsById() {
return chatsById;
}
public void setChatsById(Collection<Chats> chatsById) {
this.chatsById = chatsById;
}
#OneToMany(mappedBy = "userByIdTo")
public Collection<Chats> getChatsById_0() {
return chatsById_0;
}
public void setChatsById_0(Collection<Chats> chatsById_0) {
this.chatsById_0 = chatsById_0;
}
}
Please, pay attention to two #OneToMany annotations - getChatsById and getChatsById_0
Now our Chat class:
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
#Entity
#IdClass(ChatsPK.class)
public class Chats implements Serializable {
private Long idFrom;
private Long idTo;
private Object uuid;
private User userByIdFrom;
private User userByIdTo;
#Id
#Column(name = "id_from", nullable = false)
public Long getIdFrom() {
return idFrom;
}
public void setIdFrom(Long idFrom) {
this.idFrom = idFrom;
}
#Id
#Column(name = "id_to", nullable = false)
public Long getIdTo() {
return idTo;
}
public void setIdTo(Long idTo) {
this.idTo = idTo;
}
#Basic
#Column(name = "uuid", nullable = false)
public Object getUuid() {
return uuid;
}
public void setUuid(Object uuid) {
this.uuid = uuid;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Chats chats = (Chats) o;
return Objects.equals(idFrom, chats.idFrom) && Objects.equals(idTo, chats.idTo) && Objects.equals(uuid, chats.uuid);
}
#Override
public int hashCode() {
return Objects.hash(idFrom, idTo, uuid);
}
#ManyToOne
#JoinColumn(name = "id_from", referencedColumnName = "id", nullable = false)
public User getUserByIdFrom() {
return userByIdFrom;
}
public void setUserByIdFrom(User userByIdFrom) {
this.userByIdFrom = userByIdFrom;
}
#ManyToOne
#JoinColumn(name = "id_to", referencedColumnName = "id", nullable = false)
public User getUserByIdTo() {
return userByIdTo;
}
public void setUserByIdTo(User userByIdTo) {
this.userByIdTo = userByIdTo;
}
}
To be honest, I haven't figured out why it works yet myself
but, there should be more details
Meanwhile, I know for a fact that intelijIdea can generate you models from the database and this may be help you Generate Persistence Mapping by Database Schema - Detail settings for Entity Class. (This code was generated by it )

Categories

Resources