I am using an embedded id:
#Embeddable
public class EntityId {
private long id;
}
And I have the following tables:
#Entity
public class Master {
#EmbeddedId private EntityId id;
#OneToMany(mappedBy = "master")
Set<Config> configs;
}
#Entity
public class SlaveLeft {
private #EmbeddedId EntityId id;
}
#Entity
public class SlaveRight {
private #EmbeddedId EntityId id;
}
#Entity
public class Config {
#EmbeddedId private EntityId id;
#ManyToOne
#JoinColumn(name = "MASTER_ID", referencedColumnName = "id")
private Master master;
#ManyToOne
#JoinColumn(name = "LEFT_ID", referencedColumnName = "id")
private SlaveLeft slaveLeft;
#ManyToOne
#JoinColumn(name = "RIGHT_ID", referencedColumnName = "id")
private SlaveRight slaveRight;
}
I then populate the database:
final EntityManager em = injector.getInstance(EntityManager.class);
em.getTransaction().begin();
Master master = new Master(EntityId.next());
SlaveLeft slaveLeft = new SlaveLeft(EntityId.next());
SlaveRight slaveRight = new SlaveRight(EntityId.next());
Config config = new Config(EntityId.next(), master, slaveLeft, slaveRight);
em.persist(master);
em.persist(slaveLeft);
em.persist(slaveRight);
em.persist(config);
em.getTransaction().commit();
final Master master1 = em.find(Master.class, master.getId());
System.out.println(master1.getConfigs());
The problem is that the master1.getConfigs() returns empty.
What am I missing to get a working relation?
If you use another entity maneger to find your master, its configs collection will probably be populated. But here, the entity manager returns the master from its cache, and since you didn't put the config into the master's set of configs, it isn't there.
When using a bidirectional relationship, you are responsible for the maintenance of both sides of the relationship. The ORM only uses the owning side (the one without the mappedBy attribute) to decide if the relationship exists or not, but the coherence of the object graph is your responsibility.
Related
I am have three entities with relations look like this:
Customer and Order: one-to-many
Order and Article: one-to-many
Here are my Java Classes:
#Entity
#Table
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "customer_id", nullable = false)
private Customer customer;
#OneToMany(mappedBy = "order")
private List<Article> orderedArticles;
}
#Entity
#Table
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "customer_id")
private long id;
#OneToMany(mappedBy = "customer")
private List<Order> orders;
}
#Entity
#Table
public class Article implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "article_id")
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
And then I persist the order to the database:
Customer customer = createCustomer()
List<Article> articles = createArticles();
Order order = new Order(customer, articles)
entityManager.persist(order);
The order and customer were successfully persisted but strangely that the article not. Can anyone help me here what did i do wrong? How can I make one call to persist the order and parallel the customer and article will be also persisted?
Thank you very much!
Try change
#OneToMany(mappedBy = "order")
private List<Article> orderedArticles;
to
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<Article> orderedArticles;
As it mentioned in this article:
Cascading only makes sense for Parent – Child associations (the Parent entity state transition being cascaded to its Child entities). Cascading from Child to Parent is not very useful and usually, it’s a mapping code smell.
So, for example your Order - Article association should be corrected in this way:
#Entity
#Table
public class Order implements Serializable {
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<Article> orderedArticles;
}
#Entity
#Table
public class Article implements Serializable {
#ManyToOne // CascadeType.ALL should not be used here
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
The same correction should be made for the Customer - Order association.
You use bidirectional #OneToMany. So, as documentation states:
Whenever a bidirectional association is formed, the application developer must make sure both sides are in-sync at all times.
For example your Order entity should have the following methods:
#Entity
#Table
public class Order implements Serializable {
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<Article> orderedArticles;
public void addArticle(Article article) {
orderedArticles.add(article);
article.setOrder(this);
}
public void removeArticle(Article article) {
orderedArticles.remove(article);
article.setOrder(null);
}
}
to make the bidirectional association Order - Article in-sync. The same correction should be made for the Customer - Order association.
Assuming that your Order and Customer entities have the appropriate helper methods, the valid example of persisting can look like this:
Article article1 = new Article();
// ...
Article article2 = new Article();
// ...
Order order = new Order();
order.addArticle(article1);
order.addArticle(article2);
Customer customer = new Customer();
customer.addOrder(order);
entityManager.persist(customer);
So, you should start from articles creation, then add them to the order (or orders), then add your order (or orders) to the customer entity and then persist the customer. Due to the usage of CascadeType.ALL all children entities will be persisted too.
I've the following model : An Organization has a List<Contract>, each Contract may have a pricebookId and a collection of PricebookEntry. Multiple Contract entities can have the same pricebookId. The Collection<PricebookEntry> is the result of a #OneToMany association with the pricebookId as JoinColumn.
Here is the postulate: An organization has 2 contracts and both contracts have no pricebookId.
And here is my problem: During an Hibernate session where this particular Organization is involved, Hibernate throws this exception:
org.hibernate.HibernateException: Found shared references to a collection: Contract.pricebookEntries.
Indeed, both contracts have their pricebookEntries set to the same PersistentBag (same reference).
How can I fix this ?
Thank you for your help!
#Entity
public class Organization implements Serializable {
#Id
private String id;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(fetch = FetchType.EAGER, mappedBy = "organization")
private final List<Contract> contracts = new ArrayList<>();
}
#Entity
public class Contract implements Serializable {
#Id
private String id;
#Column(name = "pricebook_id")
private String pricebookId;
#JoinColumn(name = "organization_id", referencedColumnName = "id")
#ManyToOne(fetch = FetchType.LAZY)
private Organization organization;
#JoinColumn(name="pricebookId", referencedColumnName = "pricebook_id")
#OneToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
private final Collection<PricebookEntry> pricebookEntries = new ArrayList<>();
}
#Entity
public class PricebookEntry {
#Id
private String id;
private String pricebookId;
}
What you have here isn't a #OneToMany relationship, since multiple Contract entities may reference the same (set) of PricebookEntry instances.
To fix turn the Collection<PricebookEntry> into a proper entity Pricebook and have a many-to-one relationship from Contract to Pricebook.
Also lose the now redundant pricebookid since it is simply the id of the Pricebook.
When deleting a parent entity I also want to remove the associated child entities (from the database). I have tried to make use of cascade on remove as seen below but I must be doing something incorrectly.
When calling remove on the parent entity object, I recieve the error message: "The entity is still referenced elsewhere in the database". I can confirm that the only place where the entity is referenced elsewhere in the database is in the two tables below (if I manually delete the child row from the database, the remove call on the parent works fine). I have been reading about entity objects and trying different things for the last 9 hours. What am I doing wrong?
Here is my parent table:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
Here is my child table:
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne(cascade = CascadeType.REMOVE, optional = false)
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}
EDIT/UPDATE:
If I change CascadeType.REMOVE to CascadeType.ALL, the TurtleReview entities are successfully deleted from the database when deleting the parent TurtleLookup entity object. However, when calling the below function to create a new TurtleReview entity object, JPA tries to insert a new TurtleLookup entity in to the database, which throws the exception: "Entry already resides within the DB. Transaction rolled back". Below is the code executed when creating a new TurtleReview entity.
public void setDatasetReviewComplete(TurtleLookup turtle, Short year, boolean isComplete)
{
TurtleReview turtleReview = getTurtleReview(turtle, year);
if (turtleReview == null)
{
turtleReview = new TurtleReview();
turtleReview.setTurtleYear(year)
turtleReview.setTurtleType(new a.b.entity.TurtleLookup(turtle.getId(), turtle.getValue()));
}
turtleReview.setIsComplete(isComplete ? (short)1 : 0);
entityManager.persist(turtleReview);
}
try change cascade value to all or all-delete-orphan
#OneToMany(mappedBy = "turtleType", cascade = CascadeType.REMOVE)
List<TurtleReview> turtleReviews;
...
}
There might be an issue with your domain model, a part that is left out in the question. Do you possibly have circular cascades? If you have a circle of cascades and some of them are CascadeType.REMOVE and some are CascadeType.PERSIST, then Hibernate (not sure about other JPA implementation) will just do.... nothing when you call the remove() method. Without an error or exception message.
Try with hibernate #Cascade annotation:
#Cascade(value = CascadeType.ALL)
#OneToOne(mappedBy = "turtleReview") // mappedBy name of TurtleRewiew object field in TurtleLookup entity class
private TurtleLookup turtleType;
If your relationship is oneToOne you can't have oneToMany to the other side and you can't have List<TurtleReview>. If your relationship is oneToMany then your entities will be for example:
#Entity
#Table(name = "TURTLE_LOOKUP")
public class TurtleLookup implements Serializable
{
#Basic(optional = false)
#Column(name = "TURTLEID")
private int turtleid;
#Basic(optional = false)
#Column(name = "TURTLE")
private String turtle;
#OneToMany(mappedBy = "turtleType") // or add cascade = javax.persistence.CascadeType.ALL and remove #Cascade if you are not using hibernate
#Cascade(value = CascadeType.ALL)
List<TurtleReview> turtleReviews;
...
}
#Entity
#Table(name = "TURTLE_REVIEW")
public class TurtleReview implements Serializable
{
#Column(name = "TURTLE_REVIEW_ID")
private int turtleReviewId;
#Column(name = "TURTLE_YEAR")
private int turtleYear;
#ManyToOne
#JoinColumn(name = "TURTLE_ID", referencedColumnName = "TURTLEID")
private TurtleLookup turtleType;
#Column(name = "IS_COMPLETE")
private short isComplete;
...
}
I'm trying to figure out what I'm doing wrong with this, but I'm learning hibernate annotations and creating a simple library system. Basically, a book gets checked out by a person, and eventually checked in. Here's how I have it configured:
#Entity
#Table
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(nullable = false)
private String name;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "book") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false, unique = true)
private long barcode;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
#OneToMany
#JoinTable(name = "checkoutsession", joinColumns = { #JoinColumn(name = "user") }, inverseJoinColumns = { #JoinColumn(name = "id")})
private List<CheckOutSession> checkOutSessions;
}
#Entity
#Table(name = "checkoutsession", uniqueConstraints = {#UniqueConstraint(columnNames={"book", "checkIn"})})
public class CheckOutSession {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name="book", nullable=false)
private Book book;
#ManyToOne
#JoinColumn(name="user", nullable=false)
private User user;
#Column(nullable = false)
private java.sql.Timestamp checkOut;
#Column
private java.sql.Timestamp checkIn;
}
I can't figure out for the life of me what I've got configured incorrectly.
[EDIT]
when I try to pull a book it is selecting everything from checkoutsession join checkoutsession join user and dies saying "Unknown column checkoutsess1_.check_in in 'field list';
[EDIT2]
A little more context, I have a BookDAO that extends JpaRepository and when I call findAll() is what's creating that query.
[EDIT3]
Rest Class:
#RestController
#RequestMapping("rest/books")
public class BookController {
#RequestMapping(method = RequestMethod.GET)
public List findBooks() {
return bookService.getAllBooks();
}
}
Service:
#Component
public class BookService {
private BookDao bookDao;
public List getAllBooks() {
return bookDao.findAll();
}
#Autowired
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
DAO:
public interface BookDao extends JpaRepository<Book, Long> {
}
Thanks for any help!
If I run your code and make JPA generate tables based on the entities it seems to work (at least, it does run).
However, your mappings appear to be odd to me, more specifically the #JoinTable annotation. The #JoinTable annotation is commonly used when you have a join table (eg. checkoutSession in your case), but you don't want to map it because it contains no useful information except the links between those two tables.
In that case, you use the #JoinTable annotation as following:
#ManyToMany
#JoinTable(
name = "checkoutsession", // Name of the join table
joinColumns = #JoinColumn(name = "book"), // The column name in checkoutsession that links to book
inverseJoinColumns = #JoinColumn(name = "user") // The column name in checkoutsession that links to user
)
private List<User> users;
So in this case, you can directly link the Book and User entity without having to create the CheckoutSession entity.
However, in your case your join table also contains two timestamps, if you need those in your application, then you don't have to use #JoinTable but simply use #JoinColumn to link them, for example:
#OneToMany(mappedBy = "book") // The field name in CheckoutSession that links to book
private List<CheckoutSession> checkOutSessions;
This is what you should have in your Book entity. Be aware that in this case we're talking about field names not about column names. You have to enter the name of the field in CheckoutSession that maps back to the Book entity.
For more information about the #JoinTable annotation I recommend you to read this answer: JPA "#JoinTable" annotation or this article.
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;
...