Multi-Column Join in Hibernate/JPA Annotations - java

I have two entities which I would like to join through multiple columns. These columns are shared by an #Embeddable object that is shared by both entities. In the example below, Foo can have only one Bar but Bar can have multiple Foos (where AnEmbeddableObject is a unique key for Bar). Here is an example:
#Entity
#Table(name = "foo")
public class Foo {
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddableObject anEmbeddableObject;
#ManyToOne(targetEntity = Bar.class, fetch = FetchType.LAZY)
#JoinColumns( {
#JoinColumn(name = "column_1", referencedColumnName = "column_1"),
#JoinColumn(name = "column_2", referencedColumnName = "column_2"),
#JoinColumn(name = "column_3", referencedColumnName = "column_3"),
#JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
// ... rest of class
}
And the Bar class:
#Entity
#Table(name = "bar")
public class Bar {
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddableObject anEmbeddableObject;
// ... rest of class
}
Finally the AnEmbeddedObject class:
#Embeddable
public class AnEmbeddedObject {
#Column(name = "column_1")
private Long column1;
#Column(name = "column_2")
private Long column2;
#Column(name = "column_3")
private Long column3;
#Column(name = "column_4")
private Long column4;
// ... rest of class
}
Obviously the schema is poorly normalised, it is a restriction that AnEmbeddedObject's fields are repeated in each table.
The problem I have is that I receive this error when I try to start up Hibernate:
org.hibernate.AnnotationException: referencedColumnNames(column_1, column_2, column_3, column_4) of Foo.bar referencing Bar not mapped to a single property
I have tried marking the JoinColumns are not insertable and updatable, but with no luck. Is there a way to express this with Hibernate/JPA annotations?

This worked for me . In my case 2 tables foo and boo have to be joined based on 3 different columns.Please note in my case ,in boo the 3 common columns are not primary key
i.e., one to one mapping based on 3 different columns
#Entity
#Table(name = "foo")
public class foo implements Serializable
{
#Column(name="foocol1")
private String foocol1;
//add getter setter
#Column(name="foocol2")
private String foocol2;
//add getter setter
#Column(name="foocol3")
private String foocol3;
//add getter setter
private Boo boo;
private int id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "brsitem_id", updatable = false)
public int getId()
{
return this.id;
}
public void setId(int id)
{
this.id = id;
}
#OneToOne
#JoinColumns(
{
#JoinColumn(updatable=false,insertable=false, name="foocol1", referencedColumnName="boocol1"),
#JoinColumn(updatable=false,insertable=false, name="foocol2", referencedColumnName="boocol2"),
#JoinColumn(updatable=false,insertable=false, name="foocol3", referencedColumnName="boocol3")
}
)
public Boo getBoo()
{
return boo;
}
public void setBoo(Boo boo)
{
this.boo = boo;
}
}
#Entity
#Table(name = "boo")
public class Boo implements Serializable
{
private int id;
#Column(name="boocol1")
private String boocol1;
//add getter setter
#Column(name="boocol2")
private String boocol2;
//add getter setter
#Column(name="boocol3")
private String boocol3;
//add getter setter
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", updatable = false)
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
}

If this doesn't work I'm out of ideas. This way you get the 4 columns in both tables (as Bar owns them and Foo uses them to reference Bar) and the generated IDs in both entities. The set of 4 columns has to be unique in Bar so the many-to-one relation doesn't become a many-to-many.
#Embeddable
public class AnEmbeddedObject
{
#Column(name = "column_1")
private Long column1;
#Column(name = "column_2")
private Long column2;
#Column(name = "column_3")
private Long column3;
#Column(name = "column_4")
private Long column4;
}
#Entity
public class Foo
{
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "column_1", referencedColumnName = "column_1"),
#JoinColumn(name = "column_2", referencedColumnName = "column_2"),
#JoinColumn(name = "column_3", referencedColumnName = "column_3"),
#JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
}
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = {
"column_1",
"column_2",
"column_3",
"column_4"
}))
public class Bar
{
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddedObject anEmbeddedObject;
}

Hibernate is not going to make it easy for you to do what you are trying to do. From the Hibernate documentation:
Note that when using referencedColumnName to a non primary key column, the associated class has to be Serializable. Also note that the referencedColumnName to a non primary key column has to be mapped to a property having a single column (other cases might not work). (emphasis added)
So if you are unwilling to make AnEmbeddableObject the Identifier for Bar then Hibernate is not going to lazily, automatically retrieve Bar for you. You can, of course, still use HQL to write queries that join on AnEmbeddableObject, but you lose automatic fetching and life cycle maintenance if you insist on using a multi-column non-primary key for Bar.

Related

Hibernate One to many mapping override

I am facing a hibernate problem in updainting the join table in one to many mapping with hibernate. Below are my two entity class and join table entity class.
ArticleCategoryMap.java
#Entity
#Table(name = "ARTICLECATEGORYMAP")
public class ArticleCategoryMap {
private static final long serialVersionUID = -5653708523600543988L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column ( name = "id")
Long id;
#ManyToOne(targetEntity = Article.class, fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
#JoinColumn(name = "ARTICLE_ID", nullable = true, insertable = true, updatable = true)
private Article article;
#ManyToOne(targetEntity = Category.class, fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
#JoinColumn(name = "CATEGORY_ID", nullable = true, insertable = true, updatable = true)
private Category category;
//setter and getter
}
Article.java
#Entity
#Table(name = "ARTICLE")
public class Article {
private long id;
private String title;
private String description;
private String keywords;
private String content;
#Id
#GeneratedValue
#Column(name = "ARTICLE_ID")
public long getId() {
return id;
}
//setter and getter
}
Category.java
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = #JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = #JoinColumn(name = "ARTICLE_ID")
)
#CollectionId(
columns = #Column(name="id"),
type=#Type(type="long"),
generator = "sequence"
)
private Collection<Article> articles;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
public long getId() {
return id;
}
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = #JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = #JoinColumn(name = "ARTICLE_ID")
)
#CollectionId(
columns = #Column(name="id"),
type=#Type(type="long"),
generator = "sequence"
)
// setter an getter
}
Now suppose first time I have 2 elements in article table which is mapping to one entry of the category table. so the join table will look something like
Now due to some reason, I want to update the entry where the article entry will map to a new category ID. So the final DB should look like
So My problem Is how can I update this join table.
If you want one to many relationship (1 category have many articles and 1 article to 1 category) you dont need a join table.
The entity classes should look like that:
Category Entity:
Contains a Set of articles:
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
#OneToMany(mappedBy="category")
private Set<Article> articles;
......
}
Article Entity:
#Entity
#Table(name = "ARTICLE")
public class Article {
#ManyToOne
#JoinColumn(name="id", nullable=false)
private Category category;
private long id;
private String title;
private String description;
private String keywords;
private String content;
.......
}
For more details take a look at hibernate-one-to-many. Hope this helps.
Also move annotation from methods to fields. This:
private long id;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
public long getId() {
return id;
}
Should be:
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
private long id;
public long getId() {
return id;
}
Many to many relationship:
At your database you have 3 tables:
CATEGORY
ARTICLE
ARTICLECATEGORYMAP (join table)
For many to many relationship entities would be:
Category Entity:
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
private long id;
private String name;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = { #JoinColumn(name = "CATEGORY_ID") },
inverseJoinColumns = { #JoinColumn(name = "ARTICLE_ID") }
)
Set<Article > articles = new HashSet<>();
.....
}
Article Entity:
#Entity
#Table(name = "ARTICLE")
public class Article {
#Id
#GeneratedValue
#Column(name = "ARTICLE_ID")
private long id;
private String title;
private String description;
private String keywords;
private String content;
#ManyToMany(mappedBy = "articles")
private Set<Category> categories = new HashSet<>();
.......
}
For more info take a look at many-to-many ralationship

JPA / Hibernate delete entity from association table

I have the following hibernate mappings:
Class Folders:
#Entity
#Table(name = "FOLDER")
public class Folders implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "folder_seq")
#Column(name = "ID_FOL")
private Long id;
#Column(name = "NOFOLDER")
private String noFolder;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "M_DOMAIN_DN", joinColumns = {
#JoinColumn(name = "FK_FOLDER", referencedColumnName = "ID_FOL") }, inverseJoinColumns = {
#JoinColumn(name = "FK_DOMAIN", referencedColumnName = "ID_DOM") })
private List<Domain> domain = new ArrayList<Domain>();
/** GETTER + SETTER **/
}
Class Domain :
#Entity
#Table(name = "DOMAIN")
public class Domain {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "domain_seq")
#Column(name = "ID_DOM")
private Long id;
#Column(name = "CODE")
private String code;
#Column(name = "LABEL")
private String label;
public Long getId() {
/** GETTER + SETTER **/
}
Method that deletes an entity of type Folder:
public void deleteFolder(Long id) {
try {
entityManager.remove(getFolderById(id));
entityManager.flush();
} catch (PersistenceException e) {
}
}
I tried to delete the entity Folder but is not deleted in the database. Deleting manually in the database works fine, looking for help trying to solve this issue.

.IdentifierGenerationException: null id generated for:class

I have the following tables :
#Entity
#Table(name = "CUSTOMER")
public class Customers implements Serializable {
private static final long serialVersionUID = -5419345600310440297L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cust")
#SequenceGenerator(name = "cust", sequenceName = "cust_ID_SEQ")
#Column(name = "CUSTOMER_ID")
private Long id;
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "customer", cascade = CascadeType.PERSIST)
private Set<CustomerDeal> customerDeals;
//getters and setters goes here ....
}
#Entity
#Table(name = "DEALS")
public class Deals implements Serializable {
private static final long serialVersionUID = -7197428343863081750L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "deals_seq")
#SequenceGenerator(name = "deals_seq", sequenceName = "DEALS_SEQ")
#Column(name = "DEAL_ID")
private Long dealId;
#Column(name = "DEAL_NAME")
private String dealColName;
//getters setters
}
#Entity
#Table(name = "CUSTOMER_DEALS")
public class CustomerDeals implements Serializable {
private static final long serialVersionUID = -4249326793843278525L;
#EmbeddedId
private CustomerDealId customerDealId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CUSTOMER_ID", insertable = false, updatable = false)
private Customers customers;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "DEAL_ID", insertable = false, updatable = false)
private Deals deals;
//getters setters
}
#Embeddable
public class CustomerDealId implements Serializable {
private static final long serialVersionUID = 9086627167426987610L;
#Column(name = "DEAL_ID")
private Long dealId;
#Column(name = "CUSTOMER_ID")
private Long customerId;
}
however when I try to create a new customer
Customer cust - new Customer ()
cust.setName("Foo")
CustomerDeals custDeals = new CustomerDeals()
Set<CustomerDeal> custDealsSet = new HashSet<CustomerDeal>
CustomerDeal custDealsSet1 = new CustomerDeal()
CustomerDeal custDealsSet2 = new CustomerDeal()
custDealsSet1.setDeals(dealsRepository.findOne(1))//getting existing deal
custDealsSet1.customers(cust)
custDealsSet2.setDeals(dealsRepository.findOne(2))//getting existing deal
custDealsSet2.customers(cust)
custDealsSet.add(custDealsSet1)
custDealsSet.add(custDealsSet2)
cust.setCustomerDeals(custDealsSet)
customerRepository.saveAndFlush(cust)
customerRepository.saveAndFlush(cust)
I am getting
org.hibernate.id.IdentifierGenerationException: null id generated
for:class CustomerDeal
This is not duplication of this question
Your code that throws exception does not make sense so I guess it is not real code.
CustomerDeal has composite key, so you would not be able to retrieve it with dealsRepository.findOne(1), which means that you probably were retrieveing Deal not CustomerDeal but then the part would never compile:
Set<CustomerDeal> custDealsSet = new HashSet<CustomerDeal>();
custDealsSet.add(dealsRepository.findOne(1))
So, apart from that, I guess you were retrieving the existing deals. And you made a new customer. As the key of CustomerDeal depeneds on both customer and deal, both custumer and deal have to be set before persisting it which you probably forgot to do (and you got your exception). So it should look like:
Customer cust - new Customer ();
cust.setName("Foo");
CustomerDeals custDeal = new CustomerDeals();
custDeal.setCustomer(cust);
custDeal.setDeal(dealsRepository.findOne(1));
cust.getCustomerDeals().add(custDeal);
custDeal = new CustomerDeals();
custDeal.setCustomer(cust);
custDeal.setDeal(dealsRepository.findOne(2));
cust.getCustomerDeals().add(custDeal);
customerRepository.saveAndFlush(cust);
Now you are probably still in trouble. If you override the equals and hash on CustomerDeal so they are ID based (which typical code generator for entities does), both new CustomerDeals instances have them as null, so when you add them to the set the second one will override the first inserted (as null ids will be equals).
You also need to inform JPA that the ID will come from the relations.
In your CustomerDea you need to add #MapsId annotation (on both joins), like:
#MapsId("customerId")
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CUSTOMER_ID", insertable = false, updatable = false)
private Customers customers;
Finally, unless your CustomerDeal table contains additional apart from CUSTOMER_ID and Deal_ID, then, it is a simple joint table and should not be mapped at all. That way you will save yourself a lot of trouble.
The reason why you got the error mentioned above is due to a mapping issue(I cant figure out what exactly is wrong though). As a completely different approach, I have modified your mappings. I have tested this and it is working fine. The advantage for you with this mapping is that it makes the CustomerDeals class redundant. Please note that I have removed the sequences as I am using MySQL.
#Entity
#Table(name = "CUSTOMERS")
public class Customer implements Serializable {
private static final long serialVersionUID = -5419345600310440297L;
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "CUSTOMER_ID")
private Long id;
#Column(name = "NAME")
private String name;
#ManyToMany(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinTable(
name="CUSTOMER_DEALS",
joinColumns = #JoinColumn( name="CUSTOMER_ID"),
inverseJoinColumns = #JoinColumn( name="DEAL_ID")
)
private Set<Deals> deals = new HashSet<Deals>();
//Setters and Getters to follow
}
The Deals Class will be
#Entity
#Table(name = "DEALS")
public class Deals implements Serializable {
private static final long serialVersionUID = -7197428343863081750L;
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "DEAL_ID")
private Long dealId;
#Column(name = "DEAL_NAME")
private String dealColName;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "deals")
private Set<Customer> customers = new HashSet<Customer>(0);
//Setters and Getters here
}
Finally the main method which does the insert.
Customer customer = new Customer();
customer.setName("NewCust2");
Deals deals = new Deals();
deals.setDealColName("Deal2");
customer.getDeals().add(deals);
customerRepository.save(customer);

Joining with cross-reference table in hibernate and spring

I use hibernate and spring-data. There are two tables with many-to-many relationship.
#Entity
#Table(name = "FirstEntity")
public class FirstEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "first_entity_id")
private Long id;
#Column(name = "first_entiry_name")
private String name;
/* getters and setters are below*/
}
#Entity
#Table(name = "SecondEntity")
public class SecondEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "second_entity_id")
private Long id;
#Column(name = "second_entiry_name")
private String name;
#Column(name = "second_entiry_desc")
private String description;
/* getters and setters are below*/
}
And entity for cross-reference table.
#Entity
#Table(name = "FirstSecondEntity")
public class FirstSecondEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "first_second_entity_id")
private Long id;
#Column(name = "first_entity_id")
private Long firstEntityId;
#Column(name = "second_entity_id")
private Long secondEntityId;
/* getters and setters are below*/
}
I need SELECT like this
SELECT FirstEntity.name, SecondEntity.name, SecondEntity.description FROM SecondEntity INNER JOIN FirstSecondEntity ON SecondEntity.id = FirstSecondEntity.secondEntityId INNER JOIN User ON FirstEntity.id = FirstSecondEntity.firstEntityId
i.e. I need all records from cross-reference table where instead of ids there is actual info from entities.
Inserting this query into #Query annotation in my CrudRepository-extended class doesn't work because of
ERROR [main][org.hibernate.hql.internal.ast.ErrorCounter] Path expected for join!
So I need your help.
Your join table is all screwed up. In this case, you actually don't even need the join table as a hibernate mapping:
In Second Entity add the following list:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FirstSecondEntity",
joinColumns = {
#JoinColumn(name = "first_entity_id",
nullable = false,
updatable = false) },
inverseJoinColumns = {
#JoinColumn(name = "second_entity_id",
nullable = false,
updatable = false) },
)
private List<FirstEntity> firstEntities;
In FirstEntity add the following list:
#ManyToMany(fetch = FetchType.LAZY,
mappedBy = "firstEntities")
private List<SecondEntity> secondEntities;

Hibernate: Inconsistent results with #OneToOne unidirectional relationships

I'm getting inconsistent results with #OneToOne unidirectional relationships in Hibernate. Specifically, I have a User table with a couple of relationships to other tables: Foo and Bar.
Foo is mapped to User by the user's uuid property (a String). Bar is mapped to User by the user's id property (an Integer).
My entities look something like this:
#Entity
#Table(name = "User")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
#Column(name = "uuid")
private String uuid;
#OneToOne
#JoinColumn(name = "uuid", referencedColumnName = "frn_object_uuid")
private Foo foo;
#OneToOne
#JoinColumn(name = "id", referencedColumnName = "frn_user_id")
private Bar bar;
// getters and setters
}
#Entity
#Table(name = "Foo")
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
#Column(name = "frn_object_uuid")
private String objectUuid;
#Column(name = "foo")
private String foo;
// getters and setters
}
#Entity
#Table(name = "Bar")
public class Bar {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Integer id;
#Column(name = "frn_user_id")
private Integer userId;
#Column(name = "bar")
private String bar;
// getters and setters
}
When I perform a read on the User object, I get results for Foo but NULL for Bar.
What am I missing here? I'm able to get results if I make Bar a bidirectional relationship using mappedBy...very strange.
Thanks in advance!

Categories

Resources