Bidirectional #OneToOne persistence - jpa

I am starting with JPA and EclipseLink and did some experiments. But results are a little unexpected for my understanding. Here for example I have a bidirectional relation between two entities:
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToOne(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Tuition tuition;
#Entity
#Table(name = "tuition")
public class Tuition {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long fee;
// Que columna en la tabla Tuition tiene la FK
#JoinColumn(name = "student_id")
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Student student;
I have created two tests to check persisting both entities from both sides. When I try to persist from one side it works OK, both entities are persisted. But when trying to do the same from the other side Student in Tuition have a null value
Here are the tests:
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Jpa1to1Bidireccional {
private static final String PERSISTENCE_UNIT_NAME = "JPA_PU";
private EntityManagerFactory factory;
#Before
public void init() throws Exception {
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
}
#Test
public void testCase_1() {
System.out.println("Testcase_1 executes");
EntityManager em = factory.createEntityManager();
// Begin a new local transaction so that we can persist a new entity
em.getTransaction().begin();
Student s = new Student();
s.setName("Pirulo");
Tuition t = new Tuition();
t.setFee(2134L);
s.setTuition(t);
em.persist(s);
// Commit the transaction, which will cause the entity to // be stored in the
// database
em.getTransaction().commit();
em.close();
}
#Test
public void testCase_2() {
System.out.println("Testcase_2 executes");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Student s = new Student();
s.setName("Cachilo");
Tuition t = new Tuition();
t.setFee(3134L);
t.setStudent(s);
em.persist(t);
em.getTransaction().commit();
em.close();
}
Any help to understand what is going on, will be very welcomed.
Regards,

In your first case you are persisting student and then cascading a save to tuition but tuition has no student. You need to keep bidirectional relationships in sync before persisting
Second case you persist tuition and it requires fk of student so it cascade persist student first

Related

Many To Many Relationship JPA with Entity

I have an issue trying to generate multiple relationship in JPA with three Entities.
Order
Product
Modifier
I have an Entity to handle the relationship many to many.
OrderProducts (order_id and product_id)
Contains the relationship of one order can have multiple products
OrderDetails (order_products_id and modifier_id)
Contains the id of the previous relationship Order-Products and the Id of the modifier which is a set of multiple values that can affect the price of the product.
Not quite sure how to handle this kind of relationship in JPA as I'm new to it.
You need a join entity with a composite key. You will need to research it further.
Your entities:
#Entity
#Table(name = "ordertable")
#Data
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "order")
#EqualsAndHashCode.Exclude
private Set<OrderProductModifier> products;
}
#Entity
#Table(name = "product")
#Data
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#EqualsAndHashCode.Exclude
private BigDecimal unitPrice;
}
#Entity
#Table(name = "modifier")
#Data
public class Modifier {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#EqualsAndHashCode.Exclude
private BigDecimal modifier;
}
And the entity that ties it all together will need to have the foreign keys for each of the above entities, as you have noted.
#Entity
#Table(name = "orderproductmodifier")
#Data
public class OrderProductModifier {
#EmbeddedId
private OrderProductModifierId id;
#MapsId("orderId")
#ManyToOne
#EqualsAndHashCode.Exclude
#ToString.Exclude
private Order order;
#MapsId("productId")
#ManyToOne
#EqualsAndHashCode.Exclude
private Product product;
#MapsId("modifierId")
#ManyToOne
#EqualsAndHashCode.Exclude
private Modifier modifier;
}
#SuppressWarnings("serial")
#Embeddable
#Data
public class OrderProductModifierId implements Serializable {
private Long orderId;
private Long productId;
private Long modifierId;
}
This is pretty simple to use:
private void run() {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("UsersDB");
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Product product = new Product();
product.setUnitPrice(BigDecimal.TEN);
em.persist(product);
Modifier modifier = new Modifier();
modifier.setModifier(new BigDecimal(".90"));
em.persist(modifier);
Order order = new Order();
em.persist(order);
OrderProductModifier opm = new OrderProductModifier();
opm.setId(new OrderProductModifierId());
opm.setOrder(order);
opm.setProduct(product);
opm.setModifier(modifier);
em.persist(opm);
em.getTransaction().commit();
em.clear();
Order o = em.createQuery("select o from Order o join fetch o.products where o.id = 1", Order.class).getSingleResult();
System.out.println("Order for " + o.getProducts());
System.out.println("Order cost " + o.getProducts().stream().map(p->p.getProduct().getUnitPrice().multiply(p.getModifier().getModifier()).doubleValue()).collect(Collectors.summingDouble(Double::doubleValue)));
}
The above query could be better, but that will give you something to work on.

detached entity passed to persist: A class with ManyToOne relation to 2 more classes

Comment class
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "image_id")
private Image image;
User class
#OneToMany(mappedBy = "user", cascade = CascadeType.MERGE, fetch =
FetchType.EAGER)
private List<Comment> comments = new ArrayList<>();
Image class
#OneToMany(mappedBy = "image",cascade = CascadeType.MERGE,fetch =
FetchType.EAGER)
private List<Comment> comments = new ArrayList<Comment>();
These are the ManyToOne and OneToMany relations I have. I am unable to persist the Comment object due to "detached entity passed to persist: ImageHoster.model.Comment" error.
I kind of figured out the issue. In the CommentRepository class where I am persisting the Comment object, I used .merge instead of .persist
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
try{
transaction.begin();
em.merge(newComment);
instead of em.persist(newComment);

cascade persist results in null column value for a ManyToMany entity

Owner:
#Entity
public class Strategy implements Serializable {
#Id
#GeneratedValue
private Long id;
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST})
#JoinTable(name = "StrategyHost", joinColumns = {#JoinColumn(name = "strategyId")}, inverseJoinColumns = {#JoinColumn(name = "hostId")})
private Set<Host> hostName;
}
Related entity:
#Entity
public class Host {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String name;
#ManyToMany(mappedBy = "hostName")
private List<Strategy> strategies;
public Host(String name) {
this.name = name;
}
}
Test:
#Test
#Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testStrategyWithHosts() {
Strategy s = new Strategy();
Set<Host> hosts= new HashSet<>();
hosts.add(Host.builder().name("aaa").build());
hosts.add(Host.builder().name("bbb").build());
s.setHostName(hosts);
Strategy saved= strategyDao.save(s);
Set<Host> hostName = saved.getHostName();
}
debug shows the persisted saved object having Host:
Where are name values? However, if I add merge in cascade type array, name are valued. Why insert (not update managed entities) operation for related entities must have merge cascade type? Although log shows nothing suspicious:
insert into strategy...
insert into host...
insert into host...
update strategy ...
insert into strategy_host ...
insert into strategy_host ...

#OneToMany #ManyToOne bidirectional Entity won't accept new entity properly (GlassFish)

the old issue description is obsolete
#unwichtich thank you for your tip, it helped get rid of that nasty error.
I have the entities:
#Entity
#XmlRootElement
#Table(name="WAITERENTITY")
public class WaiterEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.EAGER)
#JoinColumn(name = "waiter_id")
private List<OrderEntity> orders = new ArrayList<>();
{plus usual setters and getters}
}
and
#Entity
#XmlRootElement
#Table(name="ORDERENTITY")
public class OrderEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long orderNumber;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "table_id")
private TableEntity table_;
private int sumOfMoney = 0;
private boolean finalized = false;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.LAZY)
#JoinColumn(name = "order_id")
private List<OrderItemEntity> orderItems = new ArrayList<>();
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "waiter_id")
private WaiterEntity waiter;
{plus usual setters and getters}
}
But the main problem remains. In the database, everything is as it should be:
<waiterEntities>
<waiterEntity>
<id>9</id>
<name>Jack Marston #499</name>
</waiterEntity>
<waiterEntity>
<id>10</id>
<name>Abigail Marston</name>
</waiterEntity>
</waiterEntities>
<orderEntities>
<orderEntity>
<finalized>false</finalized>
<orderNumber>12</orderNumber>
<sumOfMoney>0</sumOfMoney>
<waiter>
<id>9</id>
<name>Jack Marston #499</name>
</waiter>
</orderEntity>
</orderEntities>
But the #OneToMany relation of WaiterEntity does only return an empty list when waiter.getOrders() is called.
The method that creates a new OrderEntity is the following:
public void create(OrderEntity e) {
WaiterEntity waiter = em.find(WaiterEntity.class, e.getWaiter().getId());
if (waiter != null) {
(1) e.setWaiter(waiter);
em.persist(e);
System.out.println("after persist:\n" + e);
(2) //waiter.getOrders().add(e);
(3) //em.merge(waiter);
}
}
Edit: I observed very strange behaviour. Firstly, if the lines marked with (2) and (3) are un-commented, no OrderEntity will be persisted at all. Secondly, only the following outside statements will suffice GlassFish to persist an OrderEntity:
WaiterBean waiter = client.findByNameSingle(WaiterBean.class, "John Marston");
client.create(new OrderBean(waiter));
Where create will get an unique id of the respective WaiterEntity from the database. On the other hand, an OrderEntity will be not persisted, if no WaiterEntity id is known, as in for example:
client.create(new OrderBean(new WaiterBean("Hans")));
because this new object is not obtained from the database. The strage behaviour appears, when line marked with (1) is commented out: the first statement, with the previous obtainment of the respective WaiterEntity from the database won't work, but the second statement, that doesn't obtain any WaiterEntity from the database, will work and create an OrderEntity entry in the database. I really have a hard time understanding that.
The two commented lines (2) and (3) should assure that the WaiterEntity knows its OrderEntitys for later retrieval. But the only thing that these two lines do (or one of them, i tried that as well) is preventing any OrderEntity to be persisted into the database. It just won't do anything, and no further errors are reported, which drives me nuts...
Any ideas? Thanks in advance!
Just a quick guess is that you are adding the wrong OrderEntity instance. If you pass an entity instance to em.merge() the EntityManager creates a new instance of your entity, copies the state from the supplied entity, and makes the new copy managed. You have to use the new copy in any further actions regarding this entity.
In code:
public void create(OrderEntity e) {
WaiterEntity waiter = em.find(WaiterEntity.class, e.getWaiter().getId());
e = em.merge(e);
if (waiter != null) waiter.getOrders().add(e);
}

Error on em.getTransaction().commit(); using the JPA #Embeddable annotation

I have some problems with #Embeddable in JAVA JPA.
I have an entity class named "Author":
#Entity
#Table(name = "author")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Author.findAll", query = "SELECT a FROM Author a"),
...})
public class Author implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "aID")
private Integer aID;
#Column(name = "aName")
private String aName;
#Column(name = "aSurname")
private String aSurname;
#Column(name = "aPhone")
private Integer aPhone;
#Embedded
#AttributeOverrides({
#AttributeOverride(name="city",column=#Column(name="Address")),
#AttributeOverride(name="street",column=#Column(table="Address")),
#AttributeOverride(name="number",column=#Column(table="Address"))
}) private Address address;
// set and get methods.
}
Also I have an Embeddable class named "Address":
#Embeddable
#Table(name = "Address")
#XmlRootElement
public class Address implements Serializable
{
private static final long serialVersionUID=1L;
#Column(name="city")
private String city;
#Column(name="street")
private String street;
#Column(name="number")
private int number;
// get and set methods.
}
In my main class I want to insert this values to the database. (I use mySQL) But I am getting an error on this line: em.getTransaction.commit();
public class CreateAuthor extends javax.swing.JFrame {
private static final String PERSISTENCE_UNIT_NAME = "Project";
private static EntityManagerFactory emf;
public void CreateAuthor() {
initComponents();
}
private void ekleButtonActionPerformed(java.awt.event.ActionEvent evt) {
emf = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author author = new Author();
author.setAID(3);
author.setAName("Sheldon");
author.setASurname("Smith");
author.setAPhone(768987);
Address adr = new Address();
adr.setCity("Paris");
adr.setStreet("cinar");
adr.setNumber(12);
author.setAddress(adr);
em.persist(author);
em.getTransaction().commit(); /// error occured
em.close();
}
}
On my database side, I have Author table (aID(pk),aName,aSurname,aPhone)
Address Table (city,street,number)
Do you have any idea why an error is occured?
The goal of Embeddable is to have fields of an object (Address) stored in the same table as the entity's table (Author -> author).
If you want to save them in another table, than Address should be an entity on its own, and there should be a OneToOne or ManyToOne association between Author and Address. The mapping, as is, don't make any sense.