How do I setup a basic OneToMany relationship using a List and get Hibernate JPA to manage the sequence index number of the list automagically? Can this be done?
This is my test case (more or less);
#Table(name="Policy_Root")
public class PolicyRoot extends BaseDomainModel {
private List<Policy> policyList = new ArrayList<Policy>();
#OneToMany(targetEntity=Policy.class, mappedBy="policyRoot", cascade=CascadeType.ALL)
#IndexColumn(name="policy_sequence", base=0, nullable=false)
public List<Policy> getPolicyList() {
return policyList;
}
public void setPolicyList(List<Policy> policyList) {
this.policyList = policyList;
}
public void addPolicy(Policy policy) {
policyList.add(policy);
policy.setPolicyRoot(this);
}
public void addPolicy(int sequence, Policy policy) {
policyList.add(sequence, policy);
policy.setPolicyRoot(this);
}
}
#Entity()
#Table(name="Policy")
public class Policy extends BaseDomainModel {
/** The position of this policy record within the list of policy's belong to the parent PolicyRoot */
private int policySequence;
/** Birectional pointer to parent */
private PolicyRoot policyRoot;
#Column(name="policy_sequence")
public int getPolicySequence() {
return policySequence;
}
public void setPolicySequence(int policySequence) {
this.policySequence = policySequence;
}
#ManyToOne
#JoinColumn(name="policy_root_oid", nullable=false)
public PolicyRoot getPolicyRoot() {
return policyRoot;
}
public void setPolicyRoot(PolicyRoot policyRoot) {
this.policyRoot = policyRoot;
}
}
#Test
public void testCreation() {
Policy policy1 = new Policy();
Policy policy2 = new Policy();
// Uncomment the following and the test case works - but I don't want to manage the sequence numbers
//policy2.setPolicySequence(1);
PolicyRoot policyRoot = new PolicyRoot();
policyRoot.addPolicy(policy1);
policyRoot.addPolicy(policy2);
ServiceImplFacade.getPersistenceFacade().persistSingleItem(policyRoot);
Long oid = policyRoot.getOid();
PolicyRoot policyRootFromDB = ServiceImplFacade.getPersistenceFacade().getEntityManager().find(PolicyRoot.class, oid);
assertEquals(2, policyRootFromDB.getPolicyList().size());
}
If I uncomment the policy2.setPolicySequence(1); line then the test case passes, but I don't think I need to do this. I want Hibernate to do this for me. My understanding is that it can, but if it can't then knowing that it can't would be a good answer as well.
I've tried various combinations of setting nullable, insertable and updateable but I may have missed one.
Is this possible? - If so how?
Found the answer, - it was around getting the right combinations of nullable and insertable. Also had to make the "child index" at Integer so that it could be nullable, and there's also an "optional" flag in the following as well.
public class PolicyRoot extends BordereauxBaseDomainModel {
private List<Policy> policyList = new ArrayList<Policy>();
#OneToMany(cascade=CascadeType.ALL)
#IndexColumn(name="policy_sequence", nullable=false, base=0)
#JoinColumn(name="policy_root_oid", nullable=false)
public List<Policy> getPolicyList() {
return policyList;
}
public void setPolicyList(List<Policy> policyList) {
this.policyList = policyList;
}
}
public class Policy extends BordereauxBaseDomainModel {
/** The position of this policy record within the list of policy's belong to the parent PolicyRoot */
private Integer policySequence;
/** Birectional pointer to parent */
private PolicyRoot policyRoot;
#Column(name="policy_sequence", insertable=false, updatable=false)
public Integer getPolicySequence() {
return policySequence;
}
public void setPolicySequence(Integer policySequence) {
this.policySequence = policySequence;
}
#ManyToOne(optional=false)
#JoinColumn(name="policy_root_oid", insertable=false, updatable=false, nullable=false)
public PolicyRoot getPolicyRoot() {
return policyRoot;
}
public void setPolicyRoot(PolicyRoot policyRoot) {
this.policyRoot = policyRoot;
}
}
Found the answers on the following page after searching Google for a while.
http://opensource.atlassian.com/projects/hibernate/browse/HHH-4390
Do something like this:
#Entity
class Parent {
#OneToMany
#IndexColumn(name = "index_column")
List<Child> children;
}
#Entity
class Child {
#ManyToOne
Parent parent;
#Column(name = "index_column")
Integer index;
#PrePersist
#PreUpdate
private void prepareIndex() {
if (parent != null) {
index = parent.children.indexOf(this);
}
}
}
I'm going to post this answer since I recently had the same issue and this question, although outdated, keeps coming up in the researches.
The #IndexColumn annotation has been deprecated a long time ago and in its place it is best recommended using the #OrderColumn annotation. The second annotation not only simplifies its syntax without having to specify the base attribute, but it also avoids declaring an extra field in the detail class, in this case the policySequence field within the Policy class.
Here is the updated version of the previous snippet:
#Table(name="Policy_Root")
public class PolicyRoot extends BaseDomainModel {
private List<Policy> policyList = new ArrayList<Policy>();
#OneToMany(targetEntity=Policy.class, mappedBy="policyRoot", cascade=CascadeType.ALL)
#OrderColumn(name="policy_sequence", nullable=false)
public List<Policy> getPolicyList() {
return policyList;
}
public void setPolicyList(List<Policy> policyList) {
this.policyList = policyList;
}
public void addPolicy(Policy policy) {
policyList.add(policy);
policy.setPolicyRoot(this);
}
public void addPolicy(int sequence, Policy policy) {
policyList.add(sequence, policy);
policy.setPolicyRoot(this);
}
}
#Entity()
#Table(name="Policy")
public class Policy extends BaseDomainModel {
//No need to declare the policySequence field
/** Birectional pointer to parent */
private PolicyRoot policyRoot;
#Column(name="policy_sequence")
public int getPolicySequence() {
return policySequence;
}
public void setPolicySequence(int policySequence) {
this.policySequence = policySequence;
}
#ManyToOne
#JoinColumn(name="policy_root_oid", nullable=false)
public PolicyRoot getPolicyRoot() {
return policyRoot;
}
public void setPolicyRoot(PolicyRoot policyRoot) {
this.policyRoot = policyRoot;
}
}
Related
I am writing a PUT request API with spring and mongodb. But the save() inserts a new object instead of update the current one.
#Document("Test")
public class Expense {
#Field(name = "name")
private String expenseName;
#Field(name = "category")
private ExpenseCategory expenseCategory;
#Field(name = "amount")
private BigDecimal expenseAmount;
public Expense( String expenseName, ExpenseCategory expenseCategory, BigDecimal expenseAmount) {
this.expenseName = expenseName;
this.expenseCategory = expenseCategory;
this.expenseAmount = expenseAmount;
}
public String getExpenseName() {
return expenseName;
}
public void setExpenseName(String expenseName) {
this.expenseName = expenseName;
}
public ExpenseCategory getExpenseCategory() {
return expenseCategory;
}
public void setExpenseCategory(ExpenseCategory expenseCategory) {
this.expenseCategory = expenseCategory;
}
public BigDecimal getExpenseAmount() {
return expenseAmount;
}
public void setExpenseAmount(BigDecimal expenseAmount) {
this.expenseAmount = expenseAmount;
}
}
This is my reporsitory class
public interface ExpenseRepository extends MongoRepository<Expense, String> {
}
This is my Service class which shows how to update the class.
#Service
public class ExpenseService {
private final ExpenseRepository expenseRepository;
public ExpenseService(ExpenseRepository expenseRepository) {
this.expenseRepository = expenseRepository;
}
public void updateExpense(String id, Expense expense){
Expense savedExpense = expenseRepository.findById(id)
.orElseThrow(() -> new RuntimeException(
String.format("Cannot Find Expense by ID %s", id)));
savedExpense.setExpenseName(expense.getExpenseName());
savedExpense.setExpenseAmount(expense.getExpenseAmount());
savedExpense.setExpenseCategory(expense.getExpenseCategory());
expenseRepository.save(savedExpense);
}
}
This is my controller
#RestController
#RequestMapping("/api/expense")
public class ExpenseController {
private final ExpenseService expenseService;
public ExpenseController(ExpenseService expenseService) {
this.expenseService = expenseService;
}
#PutMapping("/{id}")
public ResponseEntity<Object> updateExpense(#PathVariable String id, #RequestBody Expense expense){
expenseService.updateExpense(id, expense);
return ResponseEntity.ok().build();
}
}
As shown in mongodb compass, mongodb auto generates an _id field for every object. So I do not define a id field or use #id annotation to define a primary for the collection. However, in the service class, expenseRepository.findById(id) retrieves the desired object and update it. Why does save() do the insert instead of update? Many thanks.
JPA Can't find the existing entry as no id field id set. You need to add an id field and set generation type to auto.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
I have an ExampleRequest entity that can optionally have one or more ExampleRequestYear. It's currently configured this way (unrelated fields and gettters/setters omitted for brevity, please let me know if you need anything else):
#Entity
#Table(name = "EXAMPLE_REQUEST")
#SequenceGenerator(name = "EXAMPLE_REQUEST_ID_SEQ", sequenceName = "EXAMPLE_REQUEST_ID_SEQ", allocationSize = 1)
#Cacheable(false)
public class ExampleRequest implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "EXAMPLE_REQUEST_ID_SEQ")
#Column(name="EXAMPLE_REQUEST_ID", nullable = false)
private Long exampleRequestId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "exampleRequest")
private List<ExampleRequestYear> exampleRequestYearList;
public ExampleRequest() {
}
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
}
return this.exampleRequestYearList;
}
public void setExampleRequestYearList(List<ExampleRequestYear> exampleRequestYearList) {
this.exampleRequestYearList = exampleRequestYearList;
}
public ExampleRequestYear addExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().add(exampleRequestYear);
exampleRequestYear.setExampleRequest(this);
return exampleRequestYear;
}
public ExampleRequestYear removeExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().remove(exampleRequestYear);
exampleRequestYear.setExampleRequest(null);
return exampleRequestYear;
}
}
#Entity
#Table(name = "EXAMPLE_REQUEST_YEAR")
#IdClass(ExampleRequestYearPK.class)
public class ExampleRequestYear implements Serializable {
#Id
#Column(nullable = false)
private Integer year;
#Id
#ManyToOne
#JoinColumn(name = "EXAMPLE_REQUEST_ID", referencedColumnName = "EXAMPLE_REQUEST_ID")
private ExampleRequest exampleRequest;
public ExampleRequestYear() {
}
public void setExampleRequest(ExampleRequest exampleRequest) {
this.exampleRequest = exampleRequest;
}
public ExampleRequest getExampleRequest() {
return exampleRequest;
}
}
Part of the code was auto-generated by the IDE and I'm still wrapping my head around JPA so there're probably design mistakes all around.
My app works (apparently) when I create a new ExampleRequest:
ExampleRequest exampleRequest = new ExampleRequest();
ExampleRequestYear exampleRequestYear = new ExampleRequestYear(2020);
request.addExampleRequestYear(exampleRequestYear);
However, I can't figure out how to edit an existing ExampleRequest because I'm unsure on how I'm meant to retrieve the linked entities. According to articles I've read, lazy fetching should be automatic, yet when I try this:
ExampleRequest exampleRequest = employeeRequestsController.getExampleRequestById(123);
System.out.println(exampleRequest.getExampleRequestYearList().size());
... I get a null pointer exception upon .size() because the getter runs but neither initialises an empty list, nor retrieves items from DB:
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
// Field is null and conditional is entered
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
// After initialisation, field is still null!
}
return this.exampleRequestYearList;
}
Also, switch to FetchType.EAGER solves this particular problem entirely. What am I missing?
Further details regarding app design. The Resource classes that handle HTTP requests interact with a set of Controller classes like this:
#Stateless(name = "ISomeActionController", mappedName = "ISomeActionController")
public class SomeActionController implements ISomeActionController {
#EJB
private IFooDAO fooDao;
#EJB
private IBarDAO barDao;
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId) {
return fooDao.getEntityById(exampleRequestId);
}
}
It's in the DAO classes where EntityManager is injected an used:
#Local
public interface IGenericDAO<T> {
public T persistEntity(T o);
public T persistEntityCommit(T o);
public void removeEntity(T o);
public void removeEntity(long id);
public T mergeEntity(T o);
public List<T> getEntitiesFindAll();
public List<T> getEntitiesFindAllActive();
public T getEntityById(Object id);
}
public interface IFooDAO extends IGenericDAO<ExampleRequest> {
public void flushDAO();
public ExampleRequest getExampleRequestById(Long exampleRequestId);
}
#Stateless(name = "IFooDAO", mappedName = "IFooDAO")
public class FooDAO extends GenericDAO<ExampleRequest> implements IFooDAO {
public FooDAO() {
super(ExampleRequest.class);
}
#Override
public void flushDAO(){
em.flush();
}
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId){
String sql = "...";
Query query = em.createNativeQuery(sql, ExampleRequest.class);
//...
}
}
I have problem, and I don't know how to solve it.
I have entity:
#Entity
#Table(name = "entity_languagetree")
#AttributeOverride(name = "id", column = #Column(name = "languagetree_id"))
public class LanguageTree extends BaseObject {
#ElementCollection(targetClass = java.lang.String.class, fetch = FetchType.EAGER)
#CollectionTable(name = "view_languagetree_to_stringlist")
private List<String> relationship = new ArrayList<>();
public LanguageTree() {
//
}
public List<String> getRelationship() {
return relationship;
}
public void setRelationship(List<String> relationship) {
this.relationship = relationship;
}
}
where BaseObject is
#MappedSuperclass
public class BaseObject {
#Id
#GeneratedValue
#Column(name = "entity_id")
private Long id;
/**
*
* #return true if the entity hasn't been persisted yet
*/
#Transient
public boolean isNew() {
return id == null;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Bean getBean() {
return null;
}
}
Work with object - in my servlet, I am calling jsVarTree() like this:
String var = jsVarTree();
My problem is, that after method jsVarTree is finished, hibernate delete my relationship list from entity LanguageTree. I don't know why! I am not calling any delete and etc.. (I AM SURE, I SPENT A LOT OF TIME IN DEBUGER!)
:
#Override
public String jsVarTree() {
TreeBuilder tb = new TreeBuilder(getLanguageList());
return tb.getJsVarString(); // THIS METHOD IS ONLY GETTER !!!!
}
#Override
public List<String> getLanguageList() {
LanguageTree lt = getLanguageTreeObject();
return lt.getRelationship();
}
#Override
public LanguageTree getLanguageTreeObject() {
long fakingId = languageTreeDao.getLastId();
ServerLogger.logDebug("LAST FAKING ID: " +fakingId);
return languageTreeDao.findOne(fakingId);
}
I found this log in loggor:
HibernateLog --> 15:01:03 DEBUG org.hibernate.SQL - delete from
view_languagetree_to_stringlist where LanguageTree_languagetree_id=?
Can somebody tell me, why hibernate call delete over my table?
I saw a table in phpmyadmin..
TABLE IS FULL.
String var = jsVarTree();
TABLE IS EMPTY.
Table is deleted after return tb.getJsVarString(); is finished.
Thank you for any help!
I'm having some issues with my entity mapping on these objects. I don't get an exception but it seems like it goes into a recursive loop
public class LabResult implements java.io.Serializable {
private Long labResultId;
private Customer customer;
private LabResultUnprocessed labResultUnprocessed;
public LabResult(){
}
public LabResult(Long labResultId) {
this.labResultId = labResultId;
}
public LabResult(Long labResultId, Customer customer, LabResultUnprocessed labResultUnprocessed) {
this.labResultId = labResultId;
this.customer = customer;
this.labResultUnprocessed = labResultUnprocessed;
}
#OneToOne(fetch=FetchType.LAZY, mappedBy="labResult")
#JoinColumn(name="lab_result_id")
public LabResultUnprocessed getLabResultUnprocessed(){
return labResultUnprocessed;
}
public void setLabResultUnprocessed(LabResultUnprocessed labResultUnprocessed) {
this.labResultUnprocessed = labResultUnprocessed;
}
The next domain is LabResultUnprocessed
#Entity
#Table(name="lab_result_unprocessed"
,schema="public"
)
public class LabResultUnprocessed implements java.io.Serializable {
private LabResult labResult;
private Boolean processedFlag;
public LabResultUnprocessed() {
}
public LabResultUnprocessed(LabResult labResult, Boolean processedFlag) {
this.labResult = labResult;
this.processedFlag = processedFlag;
}
#Id
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="lab_result_id")
public LabResult getLabResult() {
return labResult;
}
public void setLabResult(LabResult labResult) {
this.labResult = labResult;
}
Here is the LabResultUnprocessedRepository
public interface LabResultUnprocessedRepository extends CrudRepository<LabResult, String>{
#Query("select lru from LabResultUnprocessed lru "
+" join fetch lru.labResult lr "
+" where lru.labResult.labResultId = lr.labResultId "
+" and lru.processedFlag = false")
List<LabResultUnprocessed> findAllByProcessedFlag();
In my service when I call this method it seems like it goes into a recursive loop and never hits my breakpoint which is on the actual method call in this 2nd line.
List<LabResultUnprocessed> allUnprocessedResults = new ArrayList<LabResultUnprocessed>();
allUnprocessedResults = labResultUnprocessedRepository.findAllByProcessedFlag();
allUnprocessedResults.forEach(lru -> {
...////
You have two problems in this section:
#OneToOne(fetch=FetchType.LAZY, mappedBy="testResult")
#JoinColumn(name="test_result_id")
mappedBy and #JoinColumn don't go together. One end of the relationship should have one, and the other end should have the other. Neither end should have both. Remove #JoinColumn from this end to fix this.
The value of mappedBy needs to be the name of the field on the other end of the relationship - in this case, labResult.
In my project, I am having trouble writing a createCriteria query with a composite primary key. My Entity class & DAO method are given below -
#Entity
#Table(name="METRICS")
public class Metrics implements Serializable {
private static final long serialVersionUID = -2580493160757497919L;
#EmbeddedId
protected MetricsID metricsID;
#Column(name="PROJ_PERF")
private String proj_perf;
#Column(name="ANALYSIS")
private String analysis;
public String getProj_perf() {
return proj_perf;
}
public void setProj_perf(String proj_perf) {
this.proj_perf = proj_perf;
}
public String getAnalysis() {
return analysis;
}
public void setAnalysis(String analysis) {
this.analysis = analysis;
}
public MetricsID getMetricsID() {
return metricsID;
}
public void setMetricsID(MetricsID metricsID) {
this.metricsID = metricsID;
}
}
#Embeddable
public class MetricsID implements Serializable {
private static final long serialVersionUID = 4691163770334366543L;
#Column(name="PROJECT_ID")
private String project_id;
#Column(name="METRICS_NO")
private int metrics_no;
public String getProject_id() {
return project_id;
}
public void setProject_id(String project_id) {
this.project_id = project_id;
}
public int getMetrics_n0() {
return metrics_no;
}
public void setMetrics_no(int i) {
this.metrics_no = i;
}
}
#Override
#Transactional
public List<Metrics> viewMetrics(String project_id) throws Exception {
List<Metrics> metrics = (List<Metrics>)sessionFactory.getCurrentSession().
createCriteria(Metrics.class).createAlias("metricsID.project_id", "project_id_alias").
add(Restrictions.eqProperty("project_id_alias.project_id", project_id)).list();
return metrics;
}
The error I am getting is - org.hibernate.QueryException: not an association: metricsID.project_id
I searched for several similar examples, and used alias on the suggestion of one of the search results, but it's my first time using an alias. What am I doing wrong?
Why do you need to use an alias? Have you tried to access directly?
Following this example, this code should work
#Override
#Transactional
public List<Metrics> viewMetrics(String project_id) throws Exception {
List<Metrics> metrics =
(List<Metrics>) sessionFactory.getCurrentSession()
.createCriteria(Metrics.class)
.add(Restrictions.eq("metricsID.project_id", project_id))
.list();
return metrics;
}