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);
Related
My question is does the cascaded class form part of the composite key? So for example if I am querying by Question answer will the result also consider the fields inside the cascaded class?
#Embeddable
public class QuestionAnswer implements Serializable {
#Column(name = "QUESTION_ID", nullable = false)
/** The id of the question this answer applies to. */
private Long questionId;
#Column(name = "ANSWER_ID", nullable = false)
/** The answer id. */
private Long answerId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "PARTICIPANTS_REF_ID", referencedColumnName = "PARTICIPANT_REF_ID")
/** The participant that is involved in this answer.*/
private ParticipantReference participant
}
#Entity
#Table(name = "PARTICIPANTS_REF")
public class ParticipantReference implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "someIdGenerator")
#SequenceGenerator(name = "someIdGenerator", sequenceName = "SEQUENCE_GEN", allocationSize = 1)
#Column(name = "PARTICIPANT_REF_ID", nullable = false)
private Long id;
#Column(name = "AGE")
private long age;
#Column(name = "GENDER")
private String gender;
}
Short answer after comparing the hash of different objects is Yes the cascade class does form part of the composite key.
I have a case with Many to Many relationship
Following is the Book class with isbn as unique identifier
#Entity
#Table(name = "book")
public class Book implements Serializable {
private static final long serialVersionUID = -4643154384069203197L;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "isbn")
private String isbn;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "book_author",
joinColumns = { #JoinColumn(name = "bookId", referencedColumnName = "id") },
inverseJoinColumns = { #JoinColumn(name = "authorId", referencedColumnName = "id") })
private Set<Author> authors = new HashSet<>();
// Getters and setters
// hashcode and equals using only isbn
Following is the Author class with email as unique identifier
#Entity
#Table(name = "author")
public class Author implements Serializable{
private static final long serialVersionUID = -6907306347041383886L;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "email")
private String email;
#Column(name = "book")
#ManyToMany(mappedBy = "authors")
private Set<Book> books = new HashSet<>();
//getters and setters
// hashcode and equals using only "email"
I am able to achieve unique Book, as the Set checks the isbn and removes all the duplicates.
Since every Book can have many authors and vice versa I tried with Many to Many rekationship.
The problem comes with the unique email, Every book has an individual authors Set. How can I achevie author with unique email in the author table?
Help would be appreciated. Thanks in Advance
Change you #Column(name="email") into #Column(unique = true)
name="email"is redundant since by default it will grab the name of the attribute which is anyways email
I have already a user model.
Now I have created a movie model, my requirement is that whenever any existing user is going to add any movie, at that time user_id and movie_id will be store in the movie_added_by table.
Here user model needs to map one to many to movie_added_by and similarly, the movie will be mapped to movie_added_by.
For better understanding, you can refer to the DB diagram.
I really don't know how can I do by using hibernate annotation
The user model is like this:
#Getter
#Setter
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
private Integer user_id;
private String name;
}
The movie model is like this:
#Getter
#Setter
public class Movie implements Serializable
{
private static final long serialVersionUID = -6790693372846798580L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "movie_id", unique = true, nullable = false)
private Integer movie_id;
private String movie_name;
}
You probably want to create a #ManyToMany relationship between the entities. There are 2 ways of doing it (with intermediary table created explicitly or by Hibernate.
In simple approach your entities would look as following:
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
private Integer user_id;
private String name;
#ManyToMany(cascade = CascadeType.Persist)
#JoinTable(name="user_movie",
joinColumns = {#JoinColumn(name="user_id")},
inverseJoinColumns = {#JoinColumn(name="movie_id)})
private Set<Movie> movies = new HashSet<>();
}
public class Movie implements Serializable
{
private static final long serialVersionUID = -6790693372846798580L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "movie_id", unique = true, nullable = false)
private Integer movie_id;
private String movie_name;
#ManyToMany(cascade = CascadeType.Persist, mappedBy = "movies" //field from the user class responsible for mapping)
private Set<User> users = new HashSet<>()
}
So basically here you tell Hibernate to create an intermediary table and keep there correlated id's of those 2 entities. Couple of other notes here:
a) you might want to change the id variable type from Integer to Long in case your entities grow;
b) If you have annotated a column with #Id, you don't have to use unique=true and nullable = false in the column annotation;
c) remember about implementing no-args constructor;
d) remember to exclude relationship fileds from the equals(), hashCode() and the toString() methods;
There is another way, where you explicitly create a model for the table keeping relationships. This might become handy, when it turns out that You need to keep more data in the 'relationship table'. In that case, Your entities would look as following:
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
private Integer user_id;
private String name;
#OnetToMany(cascade = CascadeType.PERSIST, mappedBy = "user")
private Set<AddedMovie> addedMovies = new HashSet<>()
}
public class Movie implements Serializable
{
private static final long serialVersionUID = -6790693372846798580L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "movie_id", unique = true, nullable = false)
private Integer movie_id;
private String movie_name;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy = "movie")
private Set<AddedMovie> moviesAddedByUser = new HashSet<>();
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Entity
public class AddedMovie{
#Id
#GeneratedValue
private Long id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "movie_id")
private Movie movie;
// sine this entity has now its own lifecycle, you can add more fields here
private Integer rating;
private LocalDateTime movieAddedOn;
}
Have a problem persisting a ManyToMany relationship mapped like that
Document.java
public class Document {
.......
#ManyToMany(targetEntity = Category.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "fideuram_gup_documents_in_categories",
joinColumns = #JoinColumn(name="fk_document"),
inverseJoinColumns = #JoinColumn(name = "fk_category"))
private Set<Category> categories = new HashSet<Category>();
.......
}
where Category is one more entity of my model which I don't paste here since it doesn't carry a reverse mapping of this relation, and has just an ID and a name.
When I try to persist Document however I get the following error:
org.hibernate.PropertyAccessException: could not get a field value by reflection getter of it.ardesia.fideuram.gup.model.Category.id
I've surfed the web about it but no page relates to ManyToMany relations. Of course all the ManyToOne relations I have on the entity Document work fine.
I'm using:
spring-data-jpa:1.2.0.RELEASE
hibernate-core:4.2.2.Final
hibernate-entitymanager:4.2.2.final
UPDATE
All entities expose a default constructor and getter/setter for every field. Or,more preciselt, I'm using Spring Roo for creating the entity and it injects getters and setters automatically upon compilation.
You can instrument Hibernate how it must access your property using #javax.persistence.Access annotation; put on your mapped class with #Access.value set to
AccessType.FIELD for direct field access
AccessType.PROPERTY for accessing properties using accessors
Maybe it can help you, I already did the same, I put my code, it creates a join table:
#Entity
#Table(name = "custom_pizza")
public class CustomPizza extends BaseEntity {
private static final long serialVersionUID = 1L;
// ManyToMany instead of oneToMany in order to don't have the unique
// constraint on each primary key of the join table
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "custom_pizza_topping", joinColumns = #JoinColumn(name = "custom_pizza_id"), inverseJoinColumns = #JoinColumn(name = "topping_id"))
private Set<Topping> toppings = new HashSet<Topping>();
public void addTopping(Topping topping) {
toppings.add(topping);
}
public void removeTopping(Topping topping) {
toppings.remove(topping);
}
...
And my topping:
#Entity
#Table(name = "topping")
public class Topping extends BaseEntity {
private static final long serialVersionUID = 1L;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "price", nullable = false)
private float price;
....
and the BaseEntity
#MappedSuperclass
public abstract class BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
...
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.