Persistence a One-To-Many Relationship - jpa

I am using Java EE 6. Just want to throw it out there first
Here is the relationship. A customer can have multiple facility
Here is Customer Class
#Entity
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="CUSTOMER_FK", nullable=false)
private List<Facility> facilities;
public Customer(){
facilities = new ArrayList<Facility>();
}
public Customer(Long id, String name, List<Facility> facilities) {
this.id = id;
this.name = name;
this.facilities = facilities;
}
//set and get method
...
public void addFacility(Facility facility){
this.facilities.add(facility);
}
}
Here is Facility Class
#Entity
public class Facility {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public Facility(){}
public Facility(Long id, String name, Address address, List<Project> project) {
this.id = id;
this.name = name;
}
//Set and get method
...
}
Now in main if I just have this, then it work fine. I can see this get insert into my database
Customer customer = new Customer();
customer.setName("Wake Forest University");
EntityManagerFactory emf = Persistence.createEntityManagerFactory("EntityLibraryPU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(customer);
tx.commit();
However, as soon as I try to add facility and force FK constrain. Things start to break.
Customer customer = new Customer();
customer.setName("Wake Forest University");
Facility facility = new Facility();
facility.setName("Xia");
customer.addFacility(facility);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("EntityLibraryPU");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(customer);
tx.commit();
Here is the error. First I get double of the same "Wake Forest University" customer insert into CUSTOMER table (not name attribute is not set to unique, it is that it insert two entries at one time into the database). However, nothing get insert into my FACILITY table. Any idea why? One of the error code is Field 'customer_fk' doesn't have a default value. Maybe that can hint you guys a bit. I have no idea. When I look into the error log :Call: INSERT INTO FACILITY (ID, NAME, STREET2, STREET1, ZIPCODE, STATE, CITY, COUNTRY) VALUES (?, ?, ?, ?, ?, ?, ?, ?)bind => [2, Xia, null, null, null, null, null, null], my FACILITY table, have a field CUSTOMER_FK, which is created when I try to force one-to-many relationship between CUSTOMER and FACILITY, but the query never insert anything into that field

First I get double of the same "Wake Forest University" customer insert into CUSTOMER table
Well, you didn't define any uniqueness constraint on the name of the Customer entity so nothing prevents this (both records have different PK though). If you want to enforce uniqueness of the name, set a unique constraint:
#Column(unique=true)
String name;
One of the error code is Field 'customer_fk' doesn't have a default value.
I think that the commented line is "guilty":
tx.begin();
em.persist(customer);
//em.persist(facility); //DON'T DO THIS
tx.commit();
First, the facility doesn't know anything about its customer (this is a uni-directional association, from Customer to Facitlity), so JPA can't set the FK, hence the error message.
Second, because you're cascading all operations from the Customer to Facility (with the cascade=CascadeType.ALL in the #OneToMany annotation), you don't need to persist the facility instance.
So, just don't call persist on the facility instance, let JPA cascade things from the customer.

You get the duplicate-named customer because there is no unique constraint on the name column, so running the second test adds the same named customer once again. Add unique=true to customer name and the column will be enforced to be unique. You will then hit a problem that you cannot create a second Customer with the same name.
The Facility does not need to be explicitly saved, since that is also saved in cascade when you save the Customer.
In fact, you also cannot persist the facility directly since it doesn't have it's own table. By using a #JoinColumn, you remove the use of a join table between Customer and Facility and put the customer foreign key in the Facuility table. If you remove the #JoinColumn, then you will see an addtional join table like CUSTOMER_FACILITY, which stores the mapping of customers to facilities. Then, you can persist a facility independently.

Related

Fetch Entity Reference by Non-Primary Key Column

I have two entities, "User" and "Record", where the Record references a User but not by the User's primary key, but rather another column that is also unique, namely the username:
#Entity
public class User implements Serializable {
#Id
private Long id;
#NaturalId
private String username;
...
}
#Entity
public class Record {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "username", referencedColumnName = "username")
private User user;
...
}
When creating a new Record, using Hibernate's getReferenceById does not work:
#Transactional
public Record createRecord(Long userId) {
Record record = new Record()
record.setUser(userRepository.getReferenceById(userId));
return recordRepository.save(record);
}
The not-null constraint on the username column of the Record table is violated since the username is not loaded. This does make sense since the getReferenceById method of the JpaRepository interface just returns a proxy and would not return the username. Using the findById method solves this problem, but executes an additional query that I would like to avoid:
#Transactional
public Record createRecord(Long userId) {
Record record = new Record()
record.setUser(userRepository.findById(userId).orElseThrow(RuntimeException::new);
return recordRepository.save(record);
}
Is it possible to fetch an entity reference via a "natural ID" or another unique column?
Additional things to note:
The database schema is managed by Flyway, even in the test context.
Yes, I could just use the numeric ID as the foreign key reference, but I would like to instead use the username.
I know that I could also forgo using the #ManyToOne relationship all together and just use the username in the record class, but I am more interested in the general possibility of leveraging such unique non-primary key columns with Hibernate/Spring Data JPA to the same extent that IDs can be used.

JPA: Update mapping table alone

I have a Project and Employee entities, which has ManyToMany relationship like below.
#Entity
public class Project {
#Id #GeneratedValue
private int projectId;
private String projectName;
// has some additional columns
#ManyToMany(mappedBy = "projects")
private List<Employee> emp = new ArrayList<Employee> ();
....
.....
}
#Entity
public class Employee {
#Id #GeneratedValue
private int id;
private String firstName;
private String lastName;
#ManyToMany(cascade=CascadeType.ALL)
List<Project> projects = new ArrayList<Project> ();
....
....
}
When I use above entities, JPA create a mpping table 'Employee_Project' like below.
create table Employee_Project (emp_id integer not null, projects_projectId integer not null)
My question is, whenever new employee is added, I want to update both employee table and Employee_Project mapping table only, assume I know project id that I would like to map this employee to. (without touching project table/entity, I mean why should I provide complete project object, while saving employee entity alone, how can I do this via jpa?)
You don't need to provide the entire Project object. Use EntityManager.getReference(projectId) or JpaRepository.getOne(projectId).
Those methods will create a proxy object with the appropriate id, rather than loading the entire Project entity from the data store.
EDIT Your service method should look pretty much like the following:
#Transactional
public void createEmployee(Employee employee, Long projectId) {
employee.setProjects(List.of(projectRepository.getOne(projectId));
employeeRepository.save(employee);
}
As a side note, CascadeType.ALL (in particular, because it includes CascadeType.MERGE and CascadeType.REMOVE) doesn't make sense for #ManyToMany. Unless you're planning to create a Project by creating an Employee, CascadeType.PERSIST makes no sense, either.

Ebean ManyToOne Mapping CRUD

I have two Entities Transaction and Category with ManyToOne mapping. So many transaction can fall into have category.
#Entity
class Transaction extends Model{
#Id
public Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="cat_id", referencedColumnName="cat_id")
public Category cat;
}
#Entity
class Category extends Model{
#Id
#Column(name="cat_id")
public Long catId;
#Column(unique=true)
public String catName;
#ManyToOne
public List<Transaction> transactions
}
Now, when I add two transactions with same catName twice, it throws Unique constraint failure on catName. Is there any way I can instruct Ebean to merge Category, if CatName already exists (instead of always trying to insert)?
Also is this mapping approach correct, considering following:
If I delete Transaction, corresponding Category should not be deleted as it may be referenced by other Transactions.
Thanks for any help!
I think you have wrong annotation on Category model. If you want to list all Transaction data corresponded to any Category data. You should mark this with #OneToMany or #ManyToMany. As you marked your Transaction relation with Category as Many-To-One relationship, meaning that every Transaction has one Category associated to.
// This means every transaction has exactly one category associated
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="cat_id", referencedColumnName="cat_id")
public Category cat;
The relationship between Category with Transaction should be One-To-Many. The code below is a guidance how to fix your model:
#Entity
#Table(name = "category")
public class Category extends Model {
#Id
#Column(name="cat_id")
public Long catId;
#Column(unique=true)
public String catName;
// This means one category can have many transaction associated
#OneToMany(mappedBy = "cat")
public List<Transaction16507336> transactions;
}
It should allow you to save different Transaction object with same Category. Hope it is useful for you friend. :)
Update
The Category and Transaction model now have bi-directional relationship, this means if you have Category object you can also have Transaction object associated to, and vice versa. To save your model, you can follow this approach :
Category cat1 = Ebean.find(Category.class, 1L); // fetch category that exsist
Transaction t1 = new Transaction(); // this is new transaction
t1.cat = cat1; // cat1 category
t1.save();
Transaction t2 = new Transaction(); // this is new transaction
t2.cat = cat1; // cat1 category
t2.save();
Note: This reference may useful for you.

ebean unidirectional #OneToOne relation with unique constraint

I have a User class:
#Entity
public class User extends Model {
#Id
public Long id;
public String email;
public String name;
public String password;
}
and a driver class
#Entity
public class Driver extends Model {
#Id
public Long id;
#OneToOne (cascade = CascadeType.ALL)
#Column(unique = true)
public User user;
}
I want to make sure that the user_id is unique inside the Drivers table. But the code above does not enforce that. (I can create multiple drivers with the same user id).
Ideally, I do not want to add the #OneToOne relations in the User class because there are several different roles inside my app (e.g. driver, teacher, agent etc.) and I don't want to pollute user class with all those relations.
How can I achieve this?
I have tried this code on the model for me, and it worked. One thing to be noted, that you must use #OneToOne annotation to let the ORM knows that you have foreign key reference to other model.
The model look like following:
#Entity
// add unique constraint to user_id column
#Table(name = "driver",
uniqueConstraints = #UniqueConstraint(columnNames = "user_id")
)
public class Driver extends Model {
#Id
public Long id;
#OneToOne
#JoinColumn(name = "user_id")
public User user;
}
It will generate evolution script like this :
create table driver (
id bigint not null,
user_id bigint,
constraint uq_driver_1 unique (user_id), # unique database constraint
constraint pk_driver primary key (id)
);
So, with this method you can make sure that you will have unique user reference on driver table.
Additional Info
Because there is an additional constraint, that is not handled by framework but by the database applied on the model (such as the unique constraint), to validate the input or handling the occurred exception, you can surround Model.save() or form.get().save() expression (saving-the-model) with try-catch block to handle the PersistenceException.

Unneeded column added trying to set up OneToMany relationship

I'm trying to set up a OneToMany relationship between an author and his posts with author as the foreign key and username as primary key, using Java EE 6 and toplink+eclipselink as persistence provider.
Here is what i have:
Annotations in class User
#Id
#NotNull(message = "Please enter username")
#Size(min = 8, max = 40)
#Column(unique=true, nullable=false)
private String username;
...
private Collection<BlogEntry> blg = new ArrayList<BlogEntry>();
#OneToMany(cascade = {CascadeType.ALL}, mappedBy="user")
public Collection<BlogEntry> getBlg() {
return blg;
}
Annotations in class BlogEntry
#NotNull
#Size(min = 8, max = 40)
private String author;
...
private User user;
#ManyToOne()
public User getUser() {
return user;
}
The problem is that a new column(USER_USERNAME) is added when inserting values into BlogEntry Table, which of course shows an error of the field not existing in BlogEntry:
INSERT INTO BLOGENTRY (ID, CONTENT, AUTHOR, TITLE, CREATED, USER_USERNAME) VALUES (?, ?, ?, ?, ?, ?)
bind => [301, xxxxxxxx, xxxxxxxx, xxxxxxxx, 2011-06-07 12:49:07.014, null]
I would be very glad to learn why such a column is added or what to fix to get a simple OneToMany relationship using username and author fields. I searched and tried many tutorials but seems i'm missing something.
If you want to use an existing AUTHOR column as a foreign key, you need to map many-to-one relationship to that column as follows:
#ManyToOne()
#JoinColumn(name = 'author')
private User user;
Note that in typical case you can remove a separate author field from the class, since now it can be obtained as user.getUsername(). If you need both fields in the class, you need to mark one of them as read-only with insertable = false, updateable = false, because only one read-write field can be mapped to a particular column.