I have two entities, AP and APStatus, in a one-to-one unidirectional relationship. Only the AP needs to be able to access the APStatus. The only thing the APStatus needs to know is the AP's id, which will also serve as the primary key for the APStatus. Essentially, APStatus is like an embedded object, but I want a separate table for it. Here's what I have:
My entities
#Entity
public class AP {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID", nullable=false)
private int id;
#OneToOne
private APStatus apStatus;
//Getters and setters
}
#Entity
public class APStatus {
#Id
#Column(name="AP_ID", nullable=false)
private int apId;
//Getters and setters
}
My test
public class APRepositoryTest {
#Autowired
APRepository repository;
#Autowired
APStatusRepository statusRepository;
#Test
public void test() {
AP ap = new AP();
APStatus status = new APStatus();
statusRepository.save(status);
ap.setApStatus(status);
repository.save(ap);
status.setApId(ap.getId());
AP dbAp = repository.findOne(ap.getId());
assertNotNull(dbAp);
assertNotNull(dbAp.getApStatus());
assertEquals(dbAp.getId(), dbAp.getApStatus().getApId());
}
}
The assertEquals fails, expected: <1> but was <0>. And I already know why, I'm setting the status's apId field after I already saved the status and ap. But the problem with setting it before I save is that I would be setting the field equal to zero because after repository.save(ap) is executed, ap's id is auto generated to a new value (in this case 1). I've already experimented with making the relationship bidirectional and adding cascading effects but so far I've been unsuccessful. Can someone point me in the right direction or tell me how I can fix this? Thanks in advance.
EDIT: For now I'm going to make the relationship bidirectional and have the getter method in the APStatus class for the apId attribute look like the following. If someone has a better answer please share.
public int getApId() {
return ap.getId();
}
First, you cannot change an entities ID. It is set once and only once to identify the entity. After that, you are forcing the instance to reference something completely different, which is not supported in JPA.
That means that if the status.setApId call changes the apId field, it can only be set with the actual value.
Second, I don't quite understand the AP's apStatus reference. Does your AP have a foreign key to APStatus, or are you intending it to use the AP.ID APStatus.AP_ID relationship? My guess is that it is intended to reuse the AP.ID APStatus.AP_ID relationship as this seems more common, but only affects how you map it.
You can get this to work without changing your mappings by first saving the AP, using its ID in ApStatus and saving, then updating the relationship:
AP ap = new AP();
APStatus status = new APStatus();
repository.save(ap);
status.setApId(ap.getId());
statusRepository.save(status);
ap.setApStatus(status);
repository.save(ap);
Or you can change your mappings using JPA 2.0's derrived identities, set the cascade settings, and then only save either the the AP or the APStatus:
#Entity
public class AP {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="ID", nullable=false)
private int id;
#OneToOne(mappedBy = "ap", cascade=CascadeType.ALL, orphanRemoval = true)
private APStatus apStatus;
//Getters and setters
}
#Entity
public class APStatus {
#Id
#OneToOne
#JoinColumn(name="AP_ID")
private AP ap;
//Getters and setters
}
Then you can simply use:
AP ap = new AP();
APStatus status = new APStatus();
status.setAp(ap);
ap.setApStatus(status);
repository.save(ap);
Related
I'm starting a project to know more in detail JPA.
Context:
At the end of his internship, the student has a report to make and a presentation in front of his professor to do about the internship.
I've a database, which is called "grade_management". It must contains a "student", "presentation", "report", "professor" and a "mark" (there are several rating criteria such as expression, quality of powerpoint ...) table. But now it's empty, since I want to make it throught JPA.
I've a "Presentation" class. Which countain this:
#Entity
public class Presentation implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
private mark_id;
private int professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
}
But the Presentation table contain 2 foreign key: professor_id and mark_id.
My question is: How can I indicate that both of them are foreign key ?
I'm sorry if I'm not clear, don't hesitation to ask question.
Cordially
You shouldn't reference other entities by their ID, but by a direct reference to the entity.
Something like that :
#ManyToOne
#JoinColumn(name = "mark_id", referencedColumnName = "id")
private Mark mark; // supposed here that mark_id if link to entity `Mark`
#ManyToOne
#JoinColumn(name = "professor_id", referencedColumnName = "id") // suppose "id" is the column name of the PK inside the table Professor.
private Professor professor; // supposed here that professor_id if link to entity `Professor`
This code is supposing that you use an unidirectional relation.
For bidirectional you have to define this in the other side (Mark/Professor type)
#OneToMany(mappedBy = "professor")
private Presentation presentation;
From your explanation, it looks like you have a Database named grade_management and in that database you have "student", "presentation", "report", "professor" and a "mark" tables (i.e: which are #Entity by themselves defined in their separate respective classes )
I'm not sure whether you have defined them or not. If not then you have to define them first and then use the refactored code mentioned below.
So, you will have many-to-one relation mapping. You can annotate your foreign keys belonging to different tables using #ManyToOne annotation to indicate relation type and #JoinColumn annotation to indicate that this entity has a foreign key to the referenced table.
You can redefine your Presentation class show below:
#Entity
#Table(name = "Presentation")
public class Presentation implements Serializable {
#Id
#Column(name="presentation_id")
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
#ManyToOne
#JoinColumn(name = "mark_id")
private Mark mark_id;
#ManyToOne
#JoinColumn(name = "professor_id")
private Professor professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
//getter and setter
}
Also, if you need more information to read upon for yourself you can always checkout this Hibernate Documentation that explains everything you'll need to know.
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.
I am writing small app, using Play Framework 2.0 which uses Ebean as ORM.
So I need many-to-many relationship between User class and UserGroup class.
Here is some code:
#Entity
public class User extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
public Set<UserGroup> groups = new HashSet();
}
#Entity
public class UserGroup extends Domain {
#Id
public Long id;
public String name;
#ManyToMany(mappedBy="groups")
public Set<User> users = new HashSet();
}
Database scheme generator generates good scheme for that code with intermediate table and all work quite ok, till I using many-to-many.
So I am adding group in one request:
user.groups.add(UserGroup.find.byId(groupId));
user.update();
And trying output them to System.out in another:
System.out.println(user.groups);
And this returns:
BeanSet deferred
Quick search show that BeanSet is lazy-loading container from Ebean. But seems like it doesn't work in proper way or I missed something important.
So is there any ideas about what I am doing wrong?
You need to save associations manually
user.groups.add(UserGroup.find.byId(groupId));
user.saveManyToManyAssociations("groups");
user.update();
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
I am trying to do do JPA/Hibernate mappings to map two tables, but am getting this error. any help would be greatly appreciated!!
Restaurants.java
#Entity
#Table(name="RESTAURANTS")
public class Restaurants{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy="restaurant")
private LinkedList<Menus> menus = new LinkedList<Menus>();
/* constructors **/
public Restaurants(){
this.dateJoined = new Date();
};
/* getters and setters **/
#Id
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy = "increment")
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public LinkedList<Menus> getMenus() {return menus;}
public void setMenus(LinkedList<Menus> menus) {this.menus = menus;}
}
Menus.java
#Entity
#Table(name = "MENUS")
public class Menus {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private Long restaurantID;
#OneToMany
#JoinColumn(name="restaurant")
private Restaurants restaurant;
/* constructors */
public Menus(){}
/* getters and setters */
#Id
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy = "increment")
#Column(nullable = false)
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public Long getRestaurantID() {return restaurantID;}
public void setRestaurantID(Long restaurantID) {this.restaurantID = restaurantID;}
public void setRestaurant(Restaurants restaurant) {this.restaurant = restaurant;}
public Restaurants getRestaurant() {return restaurant;}
}
With this error
Exception in thread "main" org.hibernate.MappingException: Could not
determine type for: bb.entities.Restaurants, at table: MENUS, for
columns: [org.hibernate.mapping.Column(restaurant)] at
org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:306) at
org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:290) at
org.hibernate.mapping.Property.isValid(Property.java:217) at
org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:464)
at org.hibernate.mapping.RootClass.validate(RootClass.java:235) at
org.hibernate.cfg.Configuration.validate(Configuration.java:1362) at
org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1865)
at bb.TestMain.setUp(TestMain.java:26) at
bb.TestMain.main(TestMain.java:59)
Thanks.
It appears to be a misconception in the use of the #OneToMany annotation. The #OneToMany annotation is used to represent the 1-side in a 1:M relationship, and the inverse #ManyToOne relationship is used to represent the M-side. Therefore, a #OneToMany annotation should be defined on a collection-type in an entity and not on a normal reference type.
You should therefore:
use a #OneToOne association if that is the nature of the relationship between the entities.
or, decide which entity represents the 1-side in the 1:M relationship. Going by the use of the LinkedList class in Restaurants, I would consider the Restaurants class to be the 1-side, and use the #OneToMany annotation in the Restaurants class, while using the inverse #ManyToOne relationship in the Menus class. The refined code would be:
Restaurants.java
...
#OneToMany(mappedBy="restaurant")
private List<Menus> menus = new LinkedList<Menus>();
Menus.java
#ManyToOne
#JoinColumn(name="restaurant")
private Restaurants restaurant;
Note the change in the declaration of the menus member variable from LinkedList<Menus> to List<Menus>. Apparently, in this case, it is wiser to declare any collection with the interface-type of the collection, instead of the concrete collection class. The rationale is that the underlying JPA provider will use it's own concrete collection types at runtime, for the purpose of proxying the collection values. Hibernate for instance, will use a PeristentList at runtime, to represent the List in a managed entity, and not a LinkedList as created by the entity. If you use the concrete type, Hibernate might fail in mapping the column, or might fail in retrieving the associated records from the database; I'm not sure about the specifics of the runtime behavior, except that I know of the eventual failure.