Spring JPA + CRUD - custom query doesn't allow _ characters? - java

I'm having troubles in creating a custom query within spring, because my Entity contains an "_" character in it's parameter's name: "game_date".
My table has a column named "game_date" as well.
I have created following method:
List<Games> findByGame_dateAndOpponent(#Param("game_date") Date game_date, #Param("opponent") String opponent);
but when I start my app, it's crashing with exception of kind: "org.springframework.data.mapping.PropertyReferenceException: No property gamedate found for type Games!". After changing a parameter name to the "gameDate" both in Entity and Query method, it stopped complaining, and is actually returning expected entries. But at the same time, it doesn't return values from the column "game_date", in the search queries, which is a simple regular column of a Date type. I have no idea what's going on with all this thing.
DB I'm using is MySql.
Here comes the code itself:
Entity:
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
#Entity
#Table(name = "games")
public class Games {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_game")
private int id;
#Column(name = "game_date", columnDefinition = "DATE")
#Temporal(TemporalType.DATE)
private Date gameDate;
public Date getGame_date() {
return gameDate;
}
public void setGame_date(Date _game_date) {
this.gameDate = _game_date;
}
}
And a repository:
import java.sql.Date;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
#RepositoryRestResource
public interface GamesRepository extends CrudRepository< Games , Integer > {
List< Games > findById( #Param( "id" ) int id );
List< Games > findAll( );
List<Games> findByGameDateAndOpponent(#Param("game_date") Date game_date, #Param("opponent") String opponent);
}

The underscore is a reserved keyword in Spring Data JPA. It should be enough to remove it from your property and from its getters and setters and Hibernate will do the rest:
#Entity
#Table(name = "games")
public class Games {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_game")
private int id;
//Getter and setters for id
#Column(name = "game_date")
private Date gameDate;
public Date getGameDate() {
return gameDate;
}
public void setGameDate(Date gameDate) {
this.gameDate = gameDate;
}
}
Also, in general, try to use java naming convention for variable and field names, which is mixed case with lowercase first.
See also:
Spring Data JPA repository methods don't recognize property names with underscores

Related

Spring JPA add pagination to #ManyToMany [duplicate]

This question already has answers here:
Spring Data JPA Pageable with #ManyToMany
(2 answers)
Closed 1 year ago.
In my current project, I am using JPA and Hibernate to handle my database layer.
One of my entities has a Many-To-Many relation to itself, which works great using the #ManyToMany and the #JoinTable annotations.
My problem is that I am asked to add a pagination support to the entity. I have searched online for a solution but the closest thing I have found to a solution works on a different use case, where the relation is between 2 entities (and not an entity to itself).
This is the important parts from the entity's class:
package iob.data;
import iob.data.primarykeys.InstancePrimaryKey;
import iob.logic.exceptions.instance.InvalidBindingOperationException;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#RequiredArgsConstructor
#Entity
#Table(name = "INSTANCES")
#IdClass(InstancePrimaryKey.class)
public class InstanceEntity {
//<editor-fold desc="Primary key">
#Id
// The index would be generated from a sequence named "INSTANCES_SEQUENCE"
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INSTANCES_SEQUENCE")
// Here we create that sequence, and define it to be updated every insertion (allocationSize = 1).
#SequenceGenerator(name = "INSTANCES_SEQUENCE", sequenceName = "INSTANCES_SEQUENCE", allocationSize = 1)
private long id;
#Id
#NonNull
private String domain;
//</editor-fold>
//<editor-fold desc="Many to Many relation">
// Define a new table that would store the references
#JoinTable(name = "INSTANCES_TO_INSTANCES",
// The main columns for this attribute are the parent's primary key which is constructed from domain and id
inverseJoinColumns = {#JoinColumn(name = "PARENT_ID", referencedColumnName = "id"),
#JoinColumn(name = "PARENT_DOMAIN", referencedColumnName = "domain")},
// The referenced columns for this attribute are the child's primary key which is also constructed from domain and id
joinColumns = {#JoinColumn(name = "CHILD_ID", referencedColumnName = "id"),
#JoinColumn(name = "CHILD_DOMAIN", referencedColumnName = "domain")})
// Declare the parent's side of the relation
#ManyToMany(fetch = FetchType.LAZY)
private Set<InstanceEntity> parentInstances = new HashSet<>();
// Declare the children's size of the relation, and define that it is related to the parentInstances
#ManyToMany(mappedBy = "parentInstances", fetch = FetchType.LAZY)
private Set<InstanceEntity> childInstances = new HashSet<>();
//</editor-fold>
public void addParent(InstanceEntity parent) {
if (this.equals(parent))
throw new InvalidBindingOperationException("Cannot assign parent to himself");
parentInstances.add(parent);
}
public void addChild(InstanceEntity child) {
if (this.equals(child))
throw new InvalidBindingOperationException("Cannot assign child to himself");
childInstances.add(child);
}
#Override
public int hashCode() {
return Objects.hash(id, domain);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InstanceEntity entity = (InstanceEntity) o;
return id == entity.id && domain.equals(entity.domain);
}
}
So far I tried (probably naively) write the getter for parentInstance and pass it a Pagable object (because it works on derived-queries, so worth a shot 😅) but obviously it did nothing.
The only solution I can think of is removing the relation, and creating it manually (create the table using another entity and add a new PagingAndSortingRepository that would retrieve the relevant instances and then convert them to InstanceEntity in the service).
So, how can I add a pagination support for parentInstances and childInstances?
I have found the solution in some unexpected post. It is not about pagination, but it still solved my problem.
So the solution in my case was to add the following function to my PagingAndSortingRepository interface:
Page<InstanceEntity> findDistinctByParentInstancesIn(#Param("parent_instances") Set<InstanceEntity> parentInstances, Pageable pageable);
And then calling it is:
InstanceEntity ie = new InstanceEntity();
ie.setDomain(parentDomain);
ie.setId(Long.parseLong(parentId));
instancesDao.findDistinctByParentInstancesIn(Collections.singleton(ie), PageRequest.of(page, size));
To be honest, I'm not sure how/why it works. IntelliJ IDEA gives me an error in the repository interface Expected parameter types: Collection<Set<InstanceEntity>>.
So if someone has an explanation, I would be glad to hear it.

Is there something wrong with my method-naming in JPA?

I have a simple question about JpaRepository.
First, this is my Entity class.
package com.surveypedia.domain.pointhistory;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#NoArgsConstructor
#Getter
#Entity
#Table(name = "pointhistory")
public class PointHistory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer ph_code;
#Column(nullable = false)
private String email;
#Column(nullable = false, name = "s_code")
private Integer s_code;
#Column(nullable = false)
private Integer pointchange;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private PointHistoryType ph_type;
public PointHistory(String email, Integer s_code, Integer pointchange, PointHistoryType ph_type) {
this.email = email;
this.s_code = s_code;
this.pointchange = pointchange;
this.ph_type = ph_type;
}
}
And below is my repository interface to do CRUD operations.
package com.surveypedia.domain.pointhistory;
import com.surveypedia.tools.SQL;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface PointHistoryRepository extends JpaRepository<PointHistory, Integer> {
List<PointHistory> findByEmail(String email);
PointHistory findByS_codeAndEmailAndPh_type(Integer s_code, String email, PointHistoryType ph_type);
}
After starting my spring-boot project, I get this error :
java.lang.IllegalArgumentException: Failed to create query for method public abstract com.surveypedia.domain.pointhistory.PointHistory com.surveypedia.domain.pointhistory.PointHistoryRepository.findByS_codeAndEmailAndPh_type(java.lang.Integer,java.lang.String,com.surveypedia.domain.pointhistory.PointHistoryType)! No property s found for type PointHistory!
I tried findByEmailAndS_codeAndPh_type with proper arguments, but I got the same error log. What's the problem with my method there?
The problem is that underscore (_) is restricted to class hierarchies in spring-data-jpa mathod names. It's based on the simple convention of using camelCase in Java, which you're breaking.
Rename the field ph_code to phCode and s_code to sCode both in the entity and in the method name.

Strange and duplicated UUIDs are getting generated all the time

There is an reccuring issue with my Spring Boot application (using Oracle Java 8, Hibernate and Oracle DB).
I have following error in the logs:
WARN o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - SQL Error: 1, SQLState: 23000
ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions - ORA-00001: unique constraint (MY_SCHEMA.SYS_C0057302) violated
This constraint (SYS_C0057302) is UUID being UNIQUE. (UUID VARCHAR2(32) NOT NULL UNIQUE)
I cannot provoke this behaviour running it locally (even with load tests) - locally on windows it looks fine, but on RHEL (where it is deployed) problem occurs all the time.
Note that I have dozen more entity classes which all have UUIDs, but only this class is generating such strange duplicates all the time.
No idea how to fix it. Cannot find root cause of this.
Examples of UUIDsand classes used below:
There is a bit of normal UUIDs at the start, but after some time strange and duplicated UUIDs are being created. On 2 different RHEL envs.
Examples of normal UUIDs:
0C34561DD75D422CAD652715DF6C6E75
0CB86A03945040B9886752CC07EB116E
0DAA1A3AF2B5438F8CB9489348A92223
0EAE079E621B4D2B8E8BE445F76B14C9
0FCF05797E7E40DE8D3A9D6A3B44AAE1
12DEBCAB53C94285A4C3FF32C5A0BF8E
132A877F404D44069F78D9B74DD4BDC9
1338A8CE09B14552B78CBAD640A3CF29
136310C44374412FB5B1B8FAF7E35330
Example of strange UUIDs generated by UUID.randomUUID() - 99% of UUIDs are like that, very similiar, with 3 as number that comes up a lot:
33333330333433363333333233333339
33333330333433363333333333333336
33333330333433363333333433333330
33333330333433363333333433333332
33333330333433363333333433333333
33333330333433363333333533333330
33333330333433363333333533333333
33333330333433363333333533333339
33333330333433363333333533343332
33333330333433363333333633333332
33333330333433363333333633333334
33333330333433363333333733333333
33333330333433363333333733343335
33333330333433363333333833333333
33333330333433363333333933333332
TaskEntity class:
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import ----DashboardTaskDto;
import ----SimpleUserDto;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
#Getter
#Setter
#NoArgsConstructor
#Entity
#Table(name = "TASK")
#ToString
#EntityListeners(AuditingEntityListener.class)
#Slf4j
class TaskEntity {
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TASK_SEQ")
#SequenceGenerator(sequenceName = "TASK_SEQ", allocationSize = 1, name = "TASK_SEQ")
#Id
private Long id;
#Column(name = "KKF")
private String kkf;
#EqualsAndHashCode.Include
private UUID uuid = UUID.randomUUID();
private String customerName;
private String assignedUserName;
private String assignedUserRole;
private int dpd;
private Boolean bgk;
private String courtProceedings;
private String name;
private LocalDateTime dueDate;
private LocalDateTime doneDate;
private BigDecimal totalLiabilities;
private Long issueActivityId;
private String userId;
#Enumerated(EnumType.STRING)
private TaskStatus status;
#CreatedDate
private LocalDateTime created;
#LastModifiedDate
private LocalDateTime modified;
#Builder
public TaskEntity(String kkf, String customerName, String assignedUserName, String assignedUserRole, int dpd, Boolean bgk, String courtProceedings, String name, LocalDateTime dueDate, LocalDateTime doneDate, BigDecimal totalLiabilities, Long issueActivityId, String userId, TaskStatus status, LocalDateTime created, LocalDateTime modified) {
this.kkf = kkf;
this.customerName = customerName;
this.assignedUserName = assignedUserName;
this.assignedUserRole = assignedUserRole;
this.dpd = dpd;
this.bgk = bgk;
this.courtProceedings = courtProceedings;
this.name = name;
this.dueDate = dueDate;
this.doneDate = doneDate;
this.totalLiabilities = totalLiabilities;
this.issueActivityId = issueActivityId;
this.userId = userId;
this.status = status;
this.created = created;
this.modified = modified;
}
Task repository class:
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.repository.Repository;
import org.springframework.lang.Nullable;
import java.util.Optional;
interface TaskRepository extends Repository<TaskEntity, Long> {
TaskEntity save(TaskEntity from);
Optional<TaskEntity> findByIssueActivityId(Long id);
Page<TaskEntity> findAll(#Nullable Specification<TaskEntity> spec, Pageable pageable);
}
TaskCreator used for entity creation/updates:
class TaskCreator {
public TaskEntity from(IssueActivityEntity issueActivityEntity) {
IssueEntity issue = issueActivityEntity.getIssue();
CustomerEntity customer = issue.getCustomer();
UserEntity user = issueActivityEntity.getUser();
return TaskEntity.builder()
.kkf(customer.getKkf())
.customerName(customer.getCompanyName())
.assignedUserName(user.getName())
.assignedUserRole(user.getRole())
.dpd(issue.retrieveMaxDpd())
.bgk(customer.isBgk())
.courtProceedings(customer.getCourtProceedings())
.name(issueActivityEntity.getActivity().getStatus())
.dueDate(issueActivityEntity.getDueDate())
.doneDate(issueActivityEntity.getDoneDate())
.totalLiabilities(customer.getTotalLiabilities())
.issueActivityId(issueActivityEntity.getId())
.status(issueActivityEntity.getStatus())
.userId(user.getId())
.build();
}
TaskEntity updateFrom(final TaskEntity task, final IssueActivityEntity ia) {
IssueEntity issue = ia.getIssue();
CustomerEntity customer = issue.getCustomer();
UserEntity user = ia.getUser();
task.setKkf(customer.getKkf());
task.setCustomerName(customer.getCompanyName());
task.setAssignedUserRole(user.getRole());
task.setDpd(issue.retrieveMaxDpd());
task.setBgk(customer.isBgk());
task.setCourtProceedings(customer.getCourtProceedings());
task.setName(ia.getActivity().getStatus());
task.setDueDate(ia.getDueDate());
task.setDoneDate(ia.getDoneDate());
task.setTotalLiabilities(customer.getTotalLiabilities());
task.setIssueActivityId(ia.getId());
task.setStatus(ia.getStatus());
task.setUserId(user.getId());
return task;
}
}
Update 1:
I tried setting -Djava.security.egd=file:/dev/./urandom but this did not help at all.

JSON return nested arrays instead of objects [Spring boot + JPA + MySQL + REST]

ISSUE
Hello Guys please help me to solve this.
I ve started building a REST API and got a problem when testing the URLs that I've made. Example: when I send request to get the list of one object, the request work fine but the data syntax returned by JSON is ugly: I got in result nested arrays instead of one global array containing json Objects inside it. Check my code please, I have 2 entities now that one of them depend on the other, I used #OneToMany to make relationships between them and no error has occured. Thanks in advance.
SOLUTION
The problem is: my query was returning a list of lists by default, so I had to modify my query by adding a constructor call. check this links please: using new keyword in HQL query
Also I added #JsonIgnore annotation to ignore some properties in my entities to prevent their show. Now the data is shown as formatted as I want :D thanks for your help. Check the new result here
Update
Hello again, I realized recently, that is bad to use #JsonIgnore annotation to prevent some properties from being send in the Json response, and the best way to customize which properties to send is to use DTOs class. Thanks again kj007
Entity 1
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import lombok.Data;
#Data
#Table(name = "x_assureurs") // this is the table name in DB
#Entity(name = "Assureurs") // This tells Hibernate to make a table out of this class
public class Assureurs {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "n_assureur")
private String id;
#Column(name = "nom_assureur")
private String name;
#OneToMany(mappedBy="assureur",fetch = FetchType.LAZY)
private List<Contrats> contrats;
}
Entity 2
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import lombok.Data;
#Data
#Table(name = "contrats") // this is the table name in DB
#Entity(name = "Contrats") // This tells Hibernate to make a table out of this class
public class Contrats {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "num_contrat")
private String num;
#Column(name = "nom_police")
private String nomPolice;
#ManyToOne
#JoinColumn(name = "courtier")
private Courtiers courtier;
#ManyToOne
#JoinColumn(name = "assureur")
private Assureurs assureur;
}
Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import tn.igase.gestdoc.models.entities.Assureurs;
// This will be AUTO IMPLEMENTED by Spring into a Bean called assureurRepository
#Repository
public interface AssureurRepository extends JpaRepository<Assureurs, String> {
// CONSTANTS
String FIND_ALL_BY_CONTRATS = "SELECT DISTINCT(contrat.assureur.id) as n_assureur, assureur.name \n"
+ " FROM Contrats contrat \n" + " JOIN Assureurs assureur ON contrat.assureur.id = assureur.id ";
String BY_ONE_COURTIER = "WHERE contrat.courtier.id = :idCourtier";
// QUERIES
#Query(FIND_ALL_BY_CONTRATS)
Iterable<Assureurs> findAllByContrats();
#Query(FIND_ALL_BY_CONTRATS + BY_ONE_COURTIER)
Iterable<Object> findAllByContratsAndCourtier(#Param("idCourtier") int idCourtier);
}
Service
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tn.igase.gestdoc.daos.AssureurRepository;
import tn.igase.gestdoc.models.entities.Assureurs;
#Service
public class AssureurService {
#Autowired
AssureurRepository assureurRepository;
public Iterable<Assureurs> findAllByContrats() {
return assureurRepository.findAllByContrats();
}
}
Controller
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import tn.igase.gestdoc.models.entities.Assureurs;
import tn.igase.gestdoc.service.AssureurService;
import tn.igase.gestdoc.service.ContratService;
/**
*
* Assureur controller
*
* #author fre
*/
#RestController
#RequestMapping(path = "/api/assureurs")
public class AssureurController extends MainController {
#Autowired
private AssureurService assureurService;
/**
* Revert all assureurs that all have contrats
*
* #return list
*/
#RequestMapping(path = "/all", produces=MediaType.APPLICATION_JSON_VALUE)
public Iterable<Assureurs> getAll() {
// This returns a JSON or XML with the users
Iterable<Assureurs> assureurs = new ArrayList<>();
assureurs = assureurService.findAllByContrats();
return assureurs;
}
}
Result
Check the JSON data returned here
Your current HQL will return list of objects that’s why you are seeing result like this.
you can either return entity or ID(type) from a HQL or JPA named query..not projected/custom columns.
To order to achieve your list of object you can do it via couple of ways..
As HQL will retrun list of objects you can parse the object according to your need in your service class method.
#Query(FIND_ALL_BY_CONTRATS)
List<Object> findAllByContrats();
2. Use DTO (Which is best way to it)
STEP1: Create DTO for projected columns you want, make sure constructure meet the parameters required from hql ..for example..
#Data
public class AssureursDTO {
private Long n_assureur;
private String name;
public AssureursDTO(Long n_assureur, String name) {
this.n_assureur = n_assureur;
this.name = name;
}
}
STEP 2: define your HQL like this by passing full package path of DTO, use yours
String FIND_ALL_BY_CONTRATS = "SELECT DISTINCT new com.example.demomysql21.entity.AssureursDTO(assureur.id as n_assureur, assureur.name) \n"
+ " FROM Contrats contrat \n" + " JOIN Assureurs assureur ON contrat.assureur.id = assureur.id";
STEP 3: Now it will return you LIST
#Query(FIND_ALL_BY_CONTRATS)
List<AssureursDTO> findAllByContrats();

#OneToMany SQLIntegrityConstraintViolationException error upon insert (Hibernate)

I am having trouble inserting records with a #OneToMany relationship using Hibernate and Spring MVC. I can successfully insert records without adding anything to the #OneToMany collection. However, upon adding a collection record, it fails stating that there is a SQLIntegrityConstraintViolationException.
My current code for the mapping (annotation-style) is as follows:
Contact.java
package mil.navy.navsupbsc.entity;
import java.util.Collection;
import java.util.LinkedHashSet;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
#Entity
#Table(name = "CONTACT2")
public class Contact extends Auditable {
public Contact() {
}
// Create with mandatory fields
public Contact(long id, Salutation salutation, String firstName,
String middleInitial, String lastName,
MilitaryCivilianInformation milCivInfo) {
this.setContactId(id);
this.setSalutation(salutation);
this.setFirstName(firstName);
this.setMiddleInitial(middleInitial);
this.setLastName(lastName);
this.setMilitaryCivilianInformation(milCivInfo);
}
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#SequenceGenerator(name = "CONTACT_SEQ")
#Column(name = "CONTACT_ID")
private Long contactId;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(cascade = { CascadeType.ALL, CascadeType.REMOVE }, mappedBy = "contact", orphanRemoval = true)
private Collection<Email> emails = new LinkedHashSet<Email>();
/**
* #return the emails
*/
public Collection<Email> getEmails() {
for (Email email : emails) {
email.getEmailType();
}
return emails;
}
/**
* #param emails
* the emails to set
*/
public void setEmails(Collection<Email> emails) {
this.emails.clear();
for (Email email : emails) {
this.addEmail(email);
}
}
public void addEmail(Email email) {
email.setContact(this);
this.getEmails().add(email);
}
[...more Getters / Setters and fields]
}
Email.java
package mil.navy.navsupbsc.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import org.codehaus.jackson.annotate.JsonBackReference;
/**
* Implements Auditing Properties
*/
#Entity
#Table(name = "EMAIL")
public class Email extends Auditable {
private static final long serialVersionUID = -4833322552325183301L;
#Id
#GeneratedValue
#SequenceGenerator(name = "EMAIL_SEQ")
#Column(name = "EMAIL_ID")
private long emailId;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CONTACT_FK")
private Contact contact;
[More fields]
public Contact getContact() {
return contact;
}
public void setContact(Contact contact) {
this.contact = contact;
}
public long getEmailId() {
return emailId;
}
public void setEmailId(long emailId) {
this.emailId = emailId;
}
[more getters / setters]
}
ContactDAOImpl (I've tried many variations of this with no success)
public void saveContact(Contact contact) {
Session session = sessionFactory.getCurrentSession();
Collection<Email> emailCollection = new LinkedHashSet<Email>();
emailCollection = contact.getEmails();
Contact contactToSave;
long contactId;
if (contact.getContactId() == 0 || contact.getContactId() == null) {
contactToSave = new Contact((long) 0, contact.getSalutation(),
contact.getFirstName(), contact.getMiddleInitial(),
contact.getLastName(),
contact.getMilitaryCivilianInformation());
session.save(contactToSave);
session.flush();
for (Email email : emailCollection) {
// email.setContact(contactToSave);
contactToSave.addEmail(email);
}
session.saveOrUpdate(contactToSave);
session.flush();
session.clear();
}
Any help with this is much appreciated. I had a previous version of this that updated records correctly, but can't seem to work out the Save new records. I also originally used the contact that I passed in from the web service, but I attempted to create a new record in the DAO to eliminate potential problems in the latest variation of my code.
Also, I know that there are many similar questions, but I have tried many of the answers with no success (hence the new question).
Thank you for your help!
UPDATE
I checked to see what ID the data layer returns after the initial save and verified (unsuccessfully) that the same ID was saved in the database. The returned ID is different than the saved ID. For example, the latest save showed the Contact ID as '1129' with the returned contact after the initial save. I did a retrieve from the database with contact ID '1129' - and it successfully returned the contact. After closing the transaction, I viewed the data directly in the database. The database showed '193' as the Contact ID instead of '1129'. Any ideas??
I figured out the issue I was having.
Prior to creating the Hibernate entity definition, I created a sequence and trigger in the database directly. I also specified the same sequence in the entity definition within the JAVA hibernate code. However, my syntax for generating the sequence was not complete.
Since the syntax for generating the sequence was incomplete, the JAVA code was using the default HIBERNATE_SEQUENCE generator, rather than the sequence I created. Hibernate returned the sequence value from HIBERNATE_SEQUENCE (creating the ID prior to the database insert). However, immediately prior to inserting the value into the database, Oracle ran my custom sequence and assigned the Contact ID to the newly generated value.
Consequently, when I tried to add a value to the collection, the Insert statement attempted to use the sequence value generated by Hibernate as the foreign key, rather than the sequence value generated by the database.
In order to ensure that the Hibernate code and database were in sync, I removed the ContactID trigger from the database. Based on another question in StackOverflow (Hibernate and Oracle Sequence), I updated the JAVA Hibernate code for the Contact.java ContactID declaration to:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CONTACT_SEQ")
#SequenceGenerator(name = "CONTACT_SEQ", sequenceName = "contact_seq", allocationSize = 1)
#Column(name = "CONTACT_ID")
private Long contactId;
Thankfully this fixed the problem. The JAVA Hibernate code now creates the ID through the sequence I specified. The database then successfully inserts those values into the database!
I was also able to simplify my save to:
public void saveContact(Contact contact) {
Session session = sessionFactory.getCurrentSession();
if (contact.getContactId() == 0 || contact.getContactId() == null) {
session.save(contact);
session.flush();
}
}
I'm sure that I'll need to change it again though to account for updating the contact - probably using saveOrUpdate() instead of save().

Categories

Resources