I am working on a web app and I am using JSF and JPA(EclipseLink). I have the tables story and story_translate, which are mapped as follows:
#Entity
#Table(name = "story")
public class Story{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String title;
private String description;
#OneToMany(mappedBy = "story", cascade=CascadeType.ALL)
private List<StoryTranslate> translateList;
//getters and setters
}
#Entity
#Table(name = "story_translate")
public class StoryTranslate{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#Column(name="STORY_ID")
private Integer storyId;
#ManyToOne
#JoinColumn(name="story_id", referencedColumnName="id", updatable=false, insertable=false)
private Story story;
//some other fields, getters and setters
}
In a ManagedBean I am doing the following:
StoryTranslate translate = new StoryTranslate(null, sessionController.getEntity().getId(), getAuthUser().getId(), language,
title, description, new Date(), false);
EntityTransaction transaction = TransactionSingleton.getActiveInstance();
Story story = storyService.read(sessionController.getEntity().getId());
if (story != null){
if (story.getTranslateList() == null){
story.setTranslateList(new ArrayList<StoryTranslate>());
}
story.getTranslateList().add(translate);
translate.setStory(story);
}
transaction.commit();
When I try to create a new StoryTranslate, I get a DatabaseException, saying the story_id cannot be null.
I have managed relationships before, but I have never seen this error.
Where is the problem?
EDIT: I am sorry, but I have forgotten about another part of the mapping(must be the lack of sleep).
The problem is that your declare the storyId property in the StoryTranslate class for the STORY_ID column but when adding a new StoryTranslate , you do not set any value to its storyId property and I believe STORY_ID column has a NOT NULL constraint and that why you get the exception saying that story_id cannot be null.
The problem should be fixed once you set the storyId property of the StoryTranslate instance before committing the transaction .
But it is strange that you map the STORY_ID column to two different properties ( storyId and story) of the StoryTranslate class . Actually you do not need to declare storyId property as this value can be retrieved from the story instance . I suggest you change the mapping of StoryTranslate to the following and your code should work fine without any changes.
#Entity
#Table(name = "story_translate")
public class StoryTranslate{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#ManyToOne
#JoinColumn(name="story_id")
private Story story;
//some other fields, getters and setters
}
Related
I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.
I have data being persisted in Spring of employees and "personal development plans". Employee is the dominant class, so to speak. It looks like this:
#Entity(name = "employee")
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private int id;
// etc...
}
Personal Development Plan looks like this:
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#ManyToOne(optional = false)
#JoinColumn(name = "EMPLOYEE_ID")
private Employee employee;
// etc..
}
In the database it is stored as a foreign key reference from PDP -> Employee.
I want to be able to load a PDP as it is in the database, with only employee id, but i always get the whole Employee object with all attributes. How do i do this?
I tried #ManyToOne(fetch = FetchType.Lazy) but this gives me the following error when fetching:
Type definition error: [simple type, class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested
exception is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no
properties discovered to create BeanSerializer (to avoid exception,
disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference
chain:
java.util.ArrayList[0]->nl.kars.lms.model.pdp.PersonalDevelopmentPlan["employee"]->nl.kars.lms.model.Employee$HibernateProxy$AAwzPX4I["hibernateLazyInitializer"])
What am i doing wrong?
If you want only the ID, why not map it without the relationship ?
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
// etc..
}
It is actually how an ORM works... mapping table(relational side) to entities(object side). And mapping between entities is not done via ids but by entity references.
So either just persist the id (so remove strong relation) or use a projection query to just get back the employee Id.
You can create getter:
public Long getEmployeeId(){
return this.employee.getId();
}
Or you can change mapping to value private Long employeeId.
Why Spring JPA does not initialize LAZY property MyChildEntity.myParentEntity (all fields are null)?
I tried to use Hibernate.initialize and #Transactional, but that doesn't help.
my service:
#Service
#Transactional
public class MyService {
#Resource
private MyChildEntityRepository myChildEntityRepository;
#Resource
private MyParentEntityRepository myParentEntityRepository;
#PostConstruct
public void init() {
MyParentEntity p = myParentEntityRepository.save(new MyParentEntity("my name"));
myChildEntityRepository.save(new MyChildEntity(p, "first value"));
myChildEntityRepository.save(new MyChildEntity(new MyParentEntity(1L, "another name"), "another value"));
// At this point both MyChildEntity's are in database and have correct foreign key value
List<MyChildEntity> result = myChildEntityRepository.findAll();
//even this doesn't help, myParentEntity property still has all fields equals to null
Hibernate.initialize(result.get(0).getMyParentEntity());
MyParentEntity p2 = result.get(0).getMyParentEntity();
//trigger proxy's method to initialize lazy field
System.out.print(p2.getName()); // null
System.out.println(p2.getId()); // null
// PROBLEM: p2 has all fields equals null
// the same for result.get(1)
// BUT, this works correct - returns (1L, "my name") entity
myParentEntityRepository.findAll();
}
}
child entity:
#Entity
public class MyChildEntity {
#Id
#SequenceGenerator(sequenceName = "CHILD_SEQ", name = "ChildSeq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ChildSeq")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "my_parent_entity_id", referencedColumnName = "id")
private MyParentEntity myParentEntity;
#Column
private String value;
// constructors, getters, setters...
parent entity:
#Entity
public class MyParentEntity {
#Id
#SequenceGenerator(sequenceName = "WORKFLOW_SEQ", name = "WorkflowSeq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "WorkflowSeq")
private Long id;
#Column
private String name;
//constructors, getters, setters...
The fetch attribute indicates when the related entities should be retrieved from the
database using the javax.persistence.FetchType enum. FetchType.EAGER means that the JPA
provider must retrieve the values when the entity is retrieved. On the other hand, FetchType.LAZY
serves as a hint to the JPA provider that it can wait and fetch the values only when the property
is first accessed (which may be never, thus saving a trip to the database). However, JPA providers
are not required to support lazy loading, so these values may be loaded eagerly anyway.
Source: Professional Java for Web Applications by Nicholas S Williams
edit:
I really apologize I took this long. Here is what I think is wrong. I don't see an instance of child entity in parent entity. It should look like this:
public class MyParentEntity {
... //other fields
#OneToMany(fetch = FetchType.LAZY, mappedBy = "myParentEntity")
private Set<MyChildEntity> myChildEntities = new HashSet<MyChildEntity>;
... //other fields or constructors or getters or setters
...
}
I hope this works. If not, then in your MyChildEntity class, there is a weird annotation inside #JoinColumn called referencedColumnName. I don't know what that is. Please remove it.
Thanks
I have an Evaluation entity that has an associated list of EvaluationEvaluator. I need to explicitly create that entity because it required an extra column "STATUS". Before I continue evaluation. I do: evaluation.setEvaluationEvaluator(listEvaluator) where listEvaluator is a list of EvaluationEvaluator type. Then persist(evaluation). When I run this, it does not throw any kind of exception. But in the database, it inserts in the Evaluation table, and not inserted into the EvaluationEvaluator table.
Below my Evaluation entity.
#Entity
public class Evaluation implements Serializable{
#Id
#GeneratedValue
private Long id;
//MORE FIELDS
#OneToMany(mappedBy="evaluation")
private List<EvaluationEvaluator> evaluators;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvalutionEvaluator Entity:
#Entity
#Table(name= "EVALUATION_EVALUATOR")
#IdClass(EvaluationEvaluatorId.class)
public class EvaluationEvaluator implements Serializable{
#Id
#Column(name="EMPLOYEE_ID", insertable=false , updatable=false)
private Long EmployeeID;
#Id
#Column(name="EVALUATION_ID", insertable=false, updatable=false)
private Long EvaluationID;
#ManyToOne
#JoinColumn(name"EMPLOYEE_ID")
private Employee employee;
#ManyToOne
#JoinColumn(name"EVALUATION_ID")
private Evaluation evaluation;
#NotNull
private String status;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvaluationEvaluatorId class
public class EvaluationEvaluatorId implements Serializable{
private Long employeeID;
private Long evaluationID;
//CONSTRUCTOR
//GETTER AND SETTERS
}
And finally, this is my EvaluationBean class
#Stateful
#Named
#LocalBean
#ConversationScoped
public class EvaluationBean {
#PersistentContext(type= PersistenceContextType.EXTENDED)
private EntityManager em;
#Inject
Conversation conversation;
private Evaluation evaluation;
//IN MY WEBPAGE I IMPLEMENT PRIMEFACES PICKLIST AND IT REQUIRE DUALIST TO HANDLE
private DualListModel<Employe> evaluators;
private EvaluationEvaluator evaluationEvaluator;
private List<EvaluationEvaluator> listEvaluators;
#Inject
private EmployeeList employeeList;
//GETTER AND SETTERS
public String begin(){
if (conversation.isTransient()){
converstaion.begin();
}
evaluationEvaluator = new EvaluationEvaluator();
listEvaluators = new ArrayList<EvaluationEvaluator>();
evaluation = new Evaluation();
List<Employee> source = employeeList.findAll();
target = new ArrayList<Employee>();
evaluators = new DualListModel<Employee>(source, target);
return "/evalution/evaluationAsig.xhtml"
}
public String save(){
Iterator<Employee> iterator = evaluators.getTarget().iterator();
while (iterator.hasNext()){
EvaluationEvaluator ev = new EvaluationEvaluator();
ev.setEmployee(iterator.next());
listEvaluators.add(ev);
}
evalution.setEvaluationEvaluators(listEvaluators);
if(evaluation.getId()==null){
em.persist(evalution);
} else{
em.merge(evalution);
}
if(!conversation.isTransient()){
convesation.end();
}
return "/evalution/evaluationsAsig.xhtml"
}
}
When I debug my application,apparently everything is correct, but I mentioned above, doesn't persist in EvaluationEvaluator table.
Your #OneToMany association is missing cascading configuration.
Add cascade = CascadeType.ALL or cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the #OneToMany annotation. JPA assumes no cascading by default so you would need to persist each EvaluationEvaluator by yourself explicitely otherwise.
UPDATE
There is another thing wrong with the code - the Ids of EvaluationEvaluators are never assigned. You have a complex key made of two Long columns. Both are marked not insertable nor updatable which tells to JPA that the id is going to be somehow generated on database level and it should not care about it. There is however no sequence configured explicitely in your entity (although it is not necessarily required) and also from your comment:
I did what you recommended but it throws the following exception. "A different object with same identifier was already associated with the session"
I assume that this is not the case and both id column values default to null or zero and are same for all EvaluationEvaluators you are trying to persist. If you'd like the database to generate the id for you automatically use #GeneratedValue - Configure JPA to let PostgreSQL generate the primary key value - here you can find explanation how to do this (the database part is database dependent, this is for PostgreSQL). The most common use case however, is to configure the sequence but let hibernate pick the next value, instructions here - Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)
I have two entities:
#Entity
public class CustomerMainInformation {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#Column(unique = true, length = 10)
private String customerNumber;
#OneToMany(mappedBy = "firstCustomerRelationship", cascade = CascadeType.ALL)
private List<CustomerRelationship> firstCustomerRelationship;
#OneToMany(mappedBy = "secondCustomerRelationship", cascade = CascadeType.ALL)
private List<CustomerRelationship> secondCustomerRelationship;
// setter & getter
}
#Entity
public class CustomerRelationship {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(columnDefinition = "first")
private CustomerMainInformation firstCustomerRelationship;
#ManyToOne
#JoinColumn(columnDefinition = "second")
private CustomerMainInformation secondCustomerRelationship;
// setter & getter
}
I executed following code:
CustomerMainInformation customerMainInformation =manager.getReference(CustomerMainInformation.class, 1L);
System.out.println(customerMainInformation.getFirstCustomerRelationship().size());
System.out.println(customerMainInformation.getSecondCustomerRelationship().size());
CustomerMainInformation customerMainInformation2 = manager.getReference(customerMainInformation.getClass(), 2L);
CustomerRelationship customerRelationship = new CustomerRelationship();
customerRelationship.setFirstCustomerRelationship(customerMainInformation2);
customerRelationship.setSecondCustomerRelationship(customerMainInformation);
manager.persist(customerRelationship);
transaction.commit();
EntityManager manager2 = factory.createEntityManager();
CustomerMainInformation customerMainInformation3 = manager2.getReference(CustomerMainInformation.class, 1L);
System.out.println(customerMainInformation3.getFirstCustomerRelationship().size());
System.out.println(customerMainInformation3.getSecondCustomerRelationship().size());
In this code relationship size increment but because .getSecondCustomerRelationship() before called, list size not changed.
If #Cacheable(false) add to CustomerRelationship the .size() return correct list size.
You don't seem to be maintaining both sides of your relationships in the code shown. Calling setSecondCustomerRelationship Will set one side of it, and this side controls the foreign key field in the db. But your java object model is out of sync with your changes unless you also add the customerRelationship to customerMainInformation's collection. JPA does not maintain your relationships for you, so if you change one side, you are responsible for keeping the other side insync.
You can either set both sides, or force the customerMainInformation to be refreshed from the database after you commit. The first option is far more efficient.