Jpa OneToMany not persist child - jpa

I've trouble on persistence of OneToMany field. These are two simplified classes
I'm using to make test.
public class User implements Serializable {
...
private String name;
...
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<PhoneNumber> phoneNumbers;
...
}
public class PhoneNumber implements Serializable {
...
private String phoneNumber;
...
#ManyToOne()
private User user;
...
}
So I want to do this:
User u = new User();
PhoneNumber p = new PhoneNumber();
u.setName("Alan");
u.getPhoneNumbers.add(p);
But when I persist the user u the phoneNumber child is not automatically persisted.
In OO way, I only need to do a one to many composition.
I use EclipseLink.
Many thanks to all for your hints.

You need to establish the relationship in both directions. Add p.setUser(u) to your code:
User u = new User();
PhoneNumber p = new PhoneNumber();
u.setName("Alan");
u.getPhoneNumbers.add(p);
p.setUser(u);

Related

Jpa not inserting the record in the join table for many to many relatioship

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.

#OneToMany Pesist Child when creating Parent Entity

I have a OneToMany Relationship (User to EmailAddress)
Maybe I'm going about this the wrong way, My Database is empty but If I want to POST a User object and add it to the Database, along with the emailAdresses object and have the EmailAddress persisted also.
I want 2 records in the Database:
1 User and 1 EmailAddress (with a fk to User table)
Service Class
Currently what I've implemented to get this to work is this:
#Service
public class UserService {
private UserRepository userRepository;
private ModelMapper modelMapper;
public UserService(UserRepository userRepository, ModelMapper modelMapper) {
this.userRepository = userRepository;
this.modelMapper = modelMapper;
//Used for mapping List
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(Configuration.AccessLevel.PRIVATE)
.setSourceNamingConvention(NamingConventions.JAVABEANS_MUTATOR);
}
public User createUser(UserCreateDTO userCreateDTO) {
User user = modelMapper.map(userCreateDTO, User.class);
//persist User to EmailAddress object
if(user.getEmailAddresses() != null){
user.getEmailAddresses().forEach(user::persistUser);
}
return userRepository.save(user);
}
public UserDTO getUserById(Long id) {
User found = userRepository.findById(id).get();
return modelMapper.map(found, UserDTO.class);
}
// .....
Which I have seen used in some bidirectional relationships
User Entity
#Entity
#Table(name = "Users")
#Getter #Setter #ToString #NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", updatable = false, nullable = false)
private Long id;
private String firstName;
private String lastName;
private int age;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<EmailAddress> emailAddresses;
Email Address Entity
#Entity
#Table(name = "Email")
#Getter #Setter #ToString #NoArgsConstructor
public class EmailAddress {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="email_id", updatable = false, nullable = false)
private Long emailId;
private String email;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
#JoinColumn(name = "user_id", nullable = false)
#JsonIgnore
private User user;
Is there a better way to set up the Join relationship?
Sample POST Request
{"firstName":"Joe", "lastName":"Bloggs", "age": 30, "emailAddresses" : [ "joe-private#email.com" , "joe-work#email.com" ] }
I guess you need to associate this email with a user as well, not just set user to email entity.
public void persistUser(EmailAddress emailAddress) {
// set this email to the user
// if email EmailAddresses list is null you might need to init it first
this.getEmailAddresses().add(emailAddress);
emailAddress.setUser(this);
}
Firstly, I believe that a method persistUser should not be a part of a service layer - due to its implementation it mostly like a Domain layer method that should be implemented within a User entity class.
Secondly, since it's a POST method you shouldn't care of emails existence - you are adding a new user with a new set of emails
Closer to a question, I'd suggest you to try this:
public class UserService {
/************/
#Autowired
private UserManager userManager;
public void addUser(UserModel model) {
User user = new User(model);
List<EmailAddress> emails = model.getEmailAddresses().stream().map(EmailAddress::new).collect(Collectors.toList());
user.setEmailAddresses(emails);
userManager.saveUser(user);
}
}
and at the User add this:
public void setEmailAddresses(List<EmailAddress> emails) {
emails.forEach(item -> item.setUser(this));
this.emailAddresses = emails;
}
And don't forget to implement constructors for entities with model paremeters

JPA returns multiple objects of the same instance when listing all entities of a class

I have a JPA entity with a list of child entities. In this case a user entity with roles attached to it.
It looks (a bit simplified - some fields/methods omitted) like this:
#Entity
public class MyUser{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long myUserId;
private String username;
#OneToMany
#JoinTable(name = "userrole",
joinColumns = {
#JoinColumn(name="myUserId", unique = true)
},
inverseJoinColumns = {
#JoinColumn(name="roleId")
}
)
private Collection<Role> roles;
public Collection<Role> getRoles() {
return roles;
}
}
If intressting, the Role entity is very simple.
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long roleId;
private String role; // a few more string fields here .
When I add two users and a few hundred roles per user I get a wierd behaviour when I list the users. Each user get's listed a few hundred times (same user = same unique id).
The problematic code:
Query q = em.createQuery("SELECT u FROM MyUser u LEFT JOIN FETCH u.roles");
Collection<MyUser> users = q.getResultList();
for(MyUser u : users){
// print/use u here
}
However, when I just access the database and do select statements, it seems fine. Every user exists only once.
I use OpenJPA 1.2 together with a IBM DB2 database in this case.
I think you have your model wrong, typically a user-role relationship is not OneToMany but "ManyToMany" so you should change your code to look something like this:
#Entity
public class MyUser{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long myUserId;
private String username;
#ManyToMany //This should be many to many
#JoinTable(name = "userrole",
joinColumns = {
#JoinColumn(name="myUserId") //The userId in the join table should
//NOT be unique because the userId can
//be many times with different roles
},
inverseJoinColumns = {
#JoinColumn(name="roleId")
}
)
private Collection<Role> roles;
public Collection<Role> getRoles() {
return roles;
}
}
Try this way and see if it works.
Also your query shouldn't need the Left Join, the roles should be fetched automatically by JPA once you use the getRoles() method on each entity (using LAZY Fetch)
Actually, it's reasonable to have #ManyToMany mapping for User and UserRole entities. The problem with your query is that it returns all the rows from the join table what I believe you don't need. So just add group by u to your query as follows:
SELECT u FROM MyUser u LEFT JOIN FETCH u.roles GROUP BY u
and you'll be done.

Related entities not loaded - EAGER ignored?

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

JPA problem one-to-one association cascade= PERSIST

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);