I have this ER model
Message 0..1 <--> 0..1 MessageDetail
PK:ID_MESSAGE PK: ID_DETAIL
NAME DETAIL
FK: ID_MESSAGE
And the relative Object mapping is:
class OnlineEventMessage {
#Id
#Column(name = "ID_EVENT_MESSAGE")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ")
private Long idEventMessage;
#OneToOne(mappedBy="onlineEventMessage", cascade=CascadeType.PERSIST)
private EventMessageAnagrafica eventMessageAnagrafica;
}
public class EventMessageAnagrafica {
#Id
#Column(name = "ID_EVENT_MESSAGE_ANAG")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ")
private Long idEventMessageAnagrafica;
#OneToOne(cascade=CascadeType.PERSIST)
#JoinColumn(name = "FK_ID_EVENT_MESSAGE")
private OnlineEventMessage<?> onlineEventMessage;
}
This test shows how I would like to handle the objects:
#Test
public void testSaveItem() {
EntityManager entityManager = factoryCont0.createEntityManager();
entityManager.getTransaction().begin();
OnlineEventMessage<String> eventMessage = new OnlineEventMessage<String>(EventType.ONLINE_REIMPIANTO_CONTRATTO);
EventMessageAnagrafica eventMessageAnagrafica = new EventMessageAnagrafica(multichannelId);
eventMessage.setEventMessageAnagrafica(eventMessageAnagrafica);
entityManager.persist(eventMessage);
entityManager.getTransaction().commit();
entityManager.close();
}
When I persist the eventMessage on the eventMessageAnagrafica it does not save the FK.
The two ways to save the underlaying association are:
1) add this line of code : eventMessageAnagrafica.setOnlineEventMessage(eventMessage);
and save the child object: entityManager.persist(eventMessageAnagrafica);
2) change the parent setter as below:
public void setEventMessageAnagrafica(EventMessageAnagrafica eventMessageAnagrafica) {
this.eventMessageAnagrafica = eventMessageAnagrafica;
if (eventMessageAnagrafica != null) {
eventMessageAnagrafica.setOnlineEventMessage(this);
}
}
Is there any other clean way to accomplish this?
P.S. Initially the FK was on the parent table Message, but the DBA told me that this wasn't a good ER design.
Kind regards
Massimo
Maintaining consistency between sides of bidirectional relationship between objects in memory is your responsibility. When saving relationship, JPA provider looks at the owning side of the relationship, that is at the side without mappedBy.
I think the second approach is the cleanest, since it maintains consistency automatically, so that you can't forget to do it. Alternatively you can create a special function for associating these entities, other than setter, and restrict access to setters.
entityManager.getTransaction().begin();
OnlineEventMessage<String> eventMessage = new OnlineEventMessage<String>(EventType.ONLINE_REIMPIANTO_CONTRATTO);
EventMessageAnagrafica eventMessageAnagrafica = new EventMessageAnagrafica(multichannelId);
eventMessage.setEventMessageAnagrafica(eventMessageAnagrafica);
//add this line
eventMessageAnagrafica.setEventMessage(eventMessage);
entityManager.persist(eventMessage);
Related
I have a Many-to-Many relationship with User and Role JPA entities. When I try to save the entities, both User and Role entities gets persisted in the table, but the junction table is not getting inserted with the records, Where am I going wrong
User Entity
#Entity
#Table(name="users")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "roles")
#ToString(exclude = "roles")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String password;
private double salary;
public User(String name, String password, double salary) {
super();
this.name = name;
this.password = password;
this.salary = salary;
}
#ManyToMany(
mappedBy = "users")
private Set<Role> roles = new HashSet<>();
public void addRole(Role role) {
this.roles.add(role);
role.getUsers().add(this);
}
}
Role Entity
#Entity
#Table(name = "roles")
#Data
#NoArgsConstructor
#EqualsAndHashCode(exclude = "users")
#ToString(exclude = "users")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String roleName;
public Role(String roleName) {
super();
this.roleName = roleName;
}
#ManyToMany
#JoinTable(
name="user_roles",
joinColumns = #JoinColumn(name="role_id", nullable = false),
inverseJoinColumns = #JoinColumn(name="user_id", nullable = false)
)
private Set<User> users = new HashSet<>();
}
Client class
#EventListener(ApplicationReadyEvent.class)
public void onApplicationStartup(ApplicationReadyEvent event) {
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
Role userRole = new Role("ROLE_USER");
Role adminRole = new Role("ROLE_ADMIN");
kiran.addRole(userRole);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(kiran);
this.userRepository.save(vinay);
}
Where am I going wrong?
You've mapped a bidirectional relationship, but are only setting one side of it in your object model - the wrong side. Should there ever be a discrepancy, the owning side controls the values of foreign keys, and since you have left the owning side empty, they aren't being set. You are responsible to set both sides of relationships and keeping them in synch with what you want in the database.
Since you don't have cascade options set on the relationships, you are also responsible for persisting the roles independently from the Users. Something more like:
public void onApplicationStartup(ApplicationReadyEvent event) {
// you might want to check to see if these roles already exists and use those instead of creating new ones
Role userRole = roleRepository.save(new Role("ROLE_USER"));
Role adminRole = roleRepository.save(new Role("ROLE_ADMIN"));
User kiran = new User("kiran", this.passwordEncoder.encode("welcome"), 4500000);
kiran.addRole(userRole);//assumes this adds the user to the role.users as well.
this.userRepository.save(kiran);
User vinay = new User("vinay", this.passwordEncoder.encode("welcome"), 4500000);
vinay.addRole(userRole);
vinay.addRole(adminRole);
this.userRepository.save(vinay);
}
Also, you are using Set in your entities with Lombok using "#EqualsAndHashCode" generation. Don't do that!
Set uses the equals/hashcode logic to determine if two objects are the same to filter out duplicates, while Lombok generates those methods to use what are mutable fields. In the case you have new entities in those sets (ie this usecase), the IDs are null and will change when set from JPA. You are better off keeping Java equals/hashcode logic if you don't know what effects those will have on your application. try using either List in your model and/or not having Lombok generate those method for you.
I am currently using 2 classes that have a OneToMany relation. One class contains catalogs (you can think of it as book); an other class contains template (you can think of it as pages). In this scenario, one template can belong only to one catalog hence I used the OneToMany relation.
My application goes very well until I restart the service. It is currently running on Hana Cloud Platform under MaxDB. I am using JPA and eclipselink (I used #AdditionalCriteria to manage my multi-tenancy as the multi-tenancy offered by JPA does not allow me to make queries on multiple tenants).
Here is an extract of my code for the Catalog:
#Entity
#Table(name = "Catalog")
#AdditionalCriteria("(:adminAccess = 1 or this.customerId=:customerId) AND (:allStatus = 1 or this.statusRecord = :statusRecord)")
public class Catalog implements Serializable {
private static final long serialVersionUID = -3906948030586841482L;
#Id
#GeneratedValue
private long id;
[...]
#OneToMany(cascade = ALL, orphanRemoval = false, fetch = EAGER, mappedBy = "catalog")
private Set<Template> templates = new HashSet<Template>();
[...]
public void setTemplate(Template template) {
this.templates.add(template);
}
}
The code for Template is the following:
#Entity
#Table(name = "Template")
#AdditionalCriteria("(:adminAccess = 1 or this.customerId=:customerId) AND (:allStatus = 1 or this.statusRecord = :statusRecord)")
public class Template implements Serializable {
private static final long serialVersionUID = 5268250318899275624L;
#Id
#GeneratedValue
private long id;
[...]
#ManyToOne(cascade = ALL, fetch = EAGER)
#JoinColumn(name = "catalog_id", referencedColumnName = "id")
private Catalog catalog;
public void setCatalog(Catalog catalog) {
this.catalog = catalog;
if(!catalog.getTemplate().contains(this))
catalog.getTemplate().add(this);
}
}
In my Servlet, I use only the Catalog to make operations. If I have to save a template, I read it from the catalog, make the modifications in the template and persist the catalog.
It works very well until I restart my service.
The catalog does not have any references to the templates anymore BUT the template still have a reference to the catalog it used to belong to.
Can you please point me into the right direction?
Thanks
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);
}
i have the following entity relationship:
SideA:
#Entity
#Table(name = "SideA")
public class SideA {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CascadeOnDelete
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sideA", cascade=CascadeType.ALL)
private List<ABAssociation> association = new ArrayList<ABAssociation>();
}
Side B:
#Entity
#Table(name = "SideB")
public class SideB {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#CascadeOnDelete
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sideB", cascade=CascadeType.ALL)
private List<ABAssociation> association = new ArrayList<ABAssociation>();
}
ABAssociation:
#Entity
#Table(name = "ABAssociation")
public class ABAssociation {
#EmbeddedId
private ABAssociationPK pk = new ABAssociationPK();
#ManyToOne(cascade=CascadeType.MERGE)
#MapsId("aId")
private SideA sideA;
#ManyToOne(cascade=CascadeType.MERGE)
#MapsId("bId")
private SideB sideB;
}
ABAssociationPK:
#Embeddable
public class ABAssociationPK implements java.io.Serializable{
private long aId;
private long bId;
}
my problem is when i delete one side, the database delete the row in ABAssociation , but still stay in cache.
test code is like the follow:
SideA a = new SideA();
SideB b = new SideB();
entitymanager.persist(a);
entitymanager.persist(b);
ABAssociation ab = new ABAssociation()
ab.setSideA(a);
ab.setSideB(b);
entitymanager.persist(ab);
a.getABAssociationList().add(ab);
b.getABAssociationList().add(ab);
a = entitymanager.merge(a);
b = entitymanager.merge(b);
entitymanager.delete(a);
Since "a" was deleted, the relationship between "a" and "b" should be deleted too.
but when i check the "b.getABAssociationList().size()" it still there, even there is no rows in ABAssociation table in DB.
it this related to the share cache issue ?
In JPA you must maintain you object's relationships.
If you remove an object, you must first remove all references to it.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side
somewhat, yes. You need to remove B's reference to ABAssociation when ABAssociation gets deleted to maintain the cache with what is in the database. You might do this by using a preremove event on ABAssociation if you cannot do it in your application.
you can force update your list using evict() like this:
getEntityManager().getEntityManagerFactory().getCache().evict(ABAssociation.class);
Got GlassFish v3. I have an one-to-many entity. The problem is, that EclipseLink seems to ignore the fetch EAGER mode.
Here is my entities.
#Entity
public class Person implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
private List<Hobby> hobbies;
// getter and setter
}
A 1:n relationship
#Entity
public class Hobby
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne
#JoinColumn
private Person person;
// getter and setter
}
And the bean
#javax.ejb.Remote
public interface Testing
{
public void addTestData();
public List<Person> getTestData();
}
#javax.ejb.Stateless
public class TestingBean implements Testing
{
#javax.persistence.PersistenceContext
private EntityManager entityManager;
public void addTestData()
{
Person p = new Person();
p.setName("JOE");
entityManager.persist(p);
Hobby h1 = new Hobby();
h1.setName("h1");
h1.setPerson(p);
entityManager.persist(h1);
}
public List<Person> getTestData()
{
TypedQuery<Person> gridQuery = entityManager.createQuery("SELECT e FROM Person e", Person.class);
return gridQuery.getResultList();
}
}
EDIT Client:
InitialContext context = new InitialContext();
Testing test = (Testing)context.lookup("java:global/dst2_1/TestingBean");
test.addTestData();
for(Person p: test.getTestData()) {
System.out.println(p.getName());
for(Hobby b : p.getHobbys()) {
System.out.println(b.getName());
}
}
context.close();
Using MySQL - Storing the data works. But if I fetch the data only the person is returned - not hobbies. Coudld you tell me what is wrong in my code?
EDIT sorry have tried so many things ... The code shown as above produces:
Exception Description: An attempt was made to traverse a
relationship using indirection that had a null Session. This often
occurs when a n entity with an uninstantiated LAZY relationship is
serialized and that lazy relationship is traversed after
serialization. To avoid this issue, ins tantiate the LAZY
relationship prior to serialization.
But the Person is returned correctly. Why does it specify LAZY while I am using EAGER?
You code looks correct. I can't see any way that the EAGER could be ignored.
Are you sure you get the error with this attribute, not another one?
Also ensure you recompile and deployed your code correctly. You most like have an old version deployed.
Make the eager object Serializable