I'm having issues while trying to update objects with JPA. The problem instead of updating an existing object, it creates new ones along with new embedded data.
Here is my java code:
First entity
#Entity
#Table(name = "worksite")
public class Worksite {
private long id;
private String name;
private Double longitude;
private Double latitude;
private Set<WorksiteDevice> worksiteDevices = new HashSet<WorksiteDevice>();
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "worksite_id")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getLongitude() {
return longitude;
}
public void setLongitude(Double longitude) {
this.longitude = longitude;
}
public Double getLatitude() {
return latitude;
}
public void setLatitude(String latitude) {
this.latitude = latitude;
}
#OneToMany(mappedBy = "worksite", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true )
public Set<WorksiteDevice> getWorksiteDevices() {
return worksiteDevices;
}
public void setWorksiteDevices(Set<WorksiteDevice> worksiteDevices) {
this.worksiteDevices = worksiteDevices;
}
}
Second Entity:
the device name has a unique constraint in the database. This is to prevent the user from entering the same device multiple times
#Entity
#Table(name = "device")
public class Device {
private long id;
private String DeviceName;
private Set<WorksiteDevice> worksiteDevices = new HashSet<WorksiteDevice>();
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "device_id")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getDeviceName() {
return name;
}
public void setName(String name) {
this.deviceName = name;
}
#OneToMany(mappedBy = "device", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
public Set<WorksiteDevice> getWorksiteDevices() {
return worksiteDevices;
}
public void setWorksiteDevices(Set<WorksiteDevice> worksiteDevices) {
this.worksiteDevices = worksiteDevices;
}
Join Table:
#Entity
#Table(name = "worksite_device")
public class WorksiteDevice {
private long id;
private Worksite worksite;
private Device device;
// additional fields
private Integer deviceCount;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "worksite_device_id")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "worksite_id")
private Worksite worksite;
public void setWorksite(Worksite worksite) {
this.worksite = worksite;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "device_id")
public void setDevice(Device device) {
this.device = device;
}
public Integer getDeviceCount() {
return deviceCount;
}
public void setDeviceCount(Integer deviceCount) {
this.deviceCount = deviceCount;
}
}
I have a DTO class in which I get from the user interface the name of the devices and the number of devices used in a worksite.
public class WorksiteDeviceDTO extends BaseDTO{
private Long id;
private int deviceCount;
private String deviceName;
private Worksite worksite;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getDeviceCount() {
return deviceCount;
}
public void setDeviceCount(Integer deviceCount) {
this.deviceCount = deviceCount;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
deviceName = deviceName;
}
}
I save the DTO objects in a list that I iterate before saving in the database. Here is how i do this:
I save first the worksite object and then the number of devices and names used in a worksite
worksiteService.saveWorksite(worksite);
final Map<String, Geraete> deviceByName = worksiteDeviceDtos.stream()
.map(worksiteDeviceDTO::getDeviceName)
.map(this::getOrSaveDeviceByName)
.collect(Collectors.toMap(
Device::getDeviceName,
Function.identity()));
worksiteDeviceDtos.forEach(worksiteDeviceDTO -> {
final WorksiteDevice toSave = new WorksiteDevice();
toSave.setWorksite(worksite);
toSave.setDevice(deviceByName.get(worksiteDeviceDTO.getDeviceName()));
toSave.setDeviceCount(worksiteDeviceDTO.getDeviceCount());
worksiteDeviceService.saveWorksiteDevice(toSave);
I check with this Method if a Devicename already in a Database exist. If so, I'll get it back. If not, I create a new Object with the new name.
#Transactional
public Device getOrSaveDeviceByName(String DeviceName) {
return DeviceNameService.findByName(DeviceName)
.orElseGet(() -> geraeteService.saveNewGeraetWithName(DeviceName));
}
when I change the name of a device in the user interface and I keep the number of devices, a new object is created with the modified name. I don't know how to solve this problem. Someone would have an idea. I also tried to work with a compound key but I had the same problem
It seems you are creating and saving a new instance all the time, regardless if you "found" a device by that name. You should filter out the elements that you found.
I have this two object Input and IndisponibleSegment with a relation one to many respectively:
#Entity
#Table(name= "input")
public class Input {
private long _id;
private String _name;
private Set<IndisponibleSegment> _indisponibleSegments;
#Column(name = "name", unique = true, nullable = false, length = 25)
public String getName() {
return _name;
}
public void setName(String inName) {
this._name = inName;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public long getId() {
return _id;
}
public void setId(long inId) {
this._id = inId;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "input", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<IndisponibleSegment> getIndisponibleSegments() {
return _indisponibleSegments;
}
public void setIndisponibleSegments(Set<IndisponibleSegment> inIndisponibleSegments) {
this._indisponibleSegments = inIndisponibleSegments;
}
}
And:
#Entity
#Table(name = "indisponible_segment")
public class IndisponibleSegment {
private Lane _lane;
private int _id;
private Input _input;
#ManyToOne(fetch=FetchType.EAGER)
#Cascade(value={org.hibernate.annotations.CascadeType.ALL})
#JoinColumn(name="input_id")
public Input getInput() {
return _input;
}
public void setInput(Input inInput) {
this._input = inInput;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id")
public int getId() {
return _id;
}
public void setId(int inId) {
this._id = inId;
}
#Enumerated(EnumType.STRING)
#Column(name = "lane", nullable = false)
public Lane getLane() {
return _lane;
}
public void setLane(Lane inLane) {
this._lane = inLane;
}
}
My problem is that everytime run a code like:
DAO dao = new DAO();
Input input = dao.get(Input.class, new Long(1));
if(input==null) {
input = new Input();
input.setName("Input 1");
}
Set<IndisponibleSegment> islist = new HashSet<>();
IndisponibleSegment is = new IndisponibleSegment();
is.setInput(input);
is.setLane(Lane.LANE_FAST);
islist.add(is);
input.setIndisponibleSegments(islist);
dao.saveOrUpdate(input);
I get a new entry in the indisponible_segments table and the old is not removed, thus still there.
I have tried all combinations I can think of: Cascade, delete-orphans, unique constranits... all. What am I doing wrong? All I want is that if I set a new the indisponibleSegments the old ones are deleted.
I am using Java 8 with Spring 4.3.1.RELEASE and Hibernate 5.2.1.Final and JPA with MySQL.
I have a row in the database:
PERSON TABLE
# ID, UID, JOINING_DATE, LASTACCESS_DATE, DISPLAY_NAME, EMAIL_ADDRESS, AVATAR, PROVIDER, AVATAR_FIREBASE, MILES_KM, NOTIFICATIONS, CONTACTABLE, DEVICE_TOKEN
'384', 'h6qQg5YfQveTaCyBEXwDMSJPqwk1', '1499148701258', '1499240111170', 'Richard', 'richardmarais#gmail.com', ?, '3', 'https://scontent.xx.fbcdn.net/v/t1.0-1/p100x100/14484731_10155363658503146_8505143722410369457_n.jpg?oh=3b6fbb0facc9457f7e8387e4853278a8&oe=59D6034D', '0', '1', NULL, 'e_6U3WAVHu0:APA91bFibmAaVTLO13vG1Aww0yLK2UzRNopLK1UalurfYRXcCbUDrTJQKOm0hiKkyxV1auEWTL9od5ek62FfJIzo1li6vrgA6CfxE6Cu2HnPkSPGaeFispI2c16UBZcLZfYGRe1i9nmf'
When I try use Hibernate to merge an object with the existing row, it throws an error that it's a duplicate. I thought because I am doing a merge, it would UPDATE the existing row.
I have the following code:
PersonServiceImpl.java
#Transactional
public class PersonServiceImpl implements PersonService {
#Override
public Person save(Person person) throws Exception {
try {
person = personDao.merge(person);
which calls:
PersonDaoImpl.java
public class PersonDaoImpl extends JpaDao<Long, Person> implements PersonDao
#Override
public Person merge(Person person) throws InstantiationException, IllegalAccessException {
Person foundPerson = null;
if (!StringUtils.isEmpty(person.getUid())) {
foundPerson = findByUid(person.getUid(), person.getProvider());
}
if (foundPerson == null && !StringUtils.isEmpty(person.getId())) {
foundPerson = findById(person.getId());
}
if ((foundPerson == null || foundPerson.getUid() == null) && person.getEmailAddress() != null) {
foundPerson = findByEmail(person.getEmailAddress());
if (foundPerson != null) {
foundPerson.setUid(person.getUid());
}
}
if (foundPerson != null && person.getUid().equals(foundPerson.getUid())) {
if (person.getLocations().isEmpty() && foundPerson.getLocations() != null && !foundPerson.getLocations().isEmpty()) {
person.setLocations(foundPerson.getLocations());
}
BeanUtils.copyProperties(person, foundPerson);
if (foundPerson.getLocations().isEmpty() && person.getLocations() != null && !person.getLocations().isEmpty()) {
foundPerson.setLocations(person.getLocations());
}
foundPerson.setLastAccessDate(System.currentTimeMillis());
if (Objects.isNull(foundPerson.getJoiningDate()) || foundPerson.getJoiningDate() == 0) {
foundPerson.setJoiningDate(System.currentTimeMillis());
}
return super.merge(foundPerson); // << line executed
} else {
return super.merge(person);
}
}
which calls:
JpaDao.java
public class JpaDao<I, T extends AbstractDomain<I>> {
protected Class<T> entityClass;
protected T merge(T entity) throws InstantiationException, IllegalAccessException {
T attached = null;
if (entity.getId() != null) {
attached = entityManager.find(entityClass, entity.getId()); // << line executed
}
if (attached == null) {
attached = entityClass.newInstance();
}
BeanUtils.copyProperties(entity, attached);
entityManager.setFlushMode(FlushModeType.COMMIT);
attached = entityManager.merge(attached);
return attached;
}
However, when I try merge the Person object, I get the following error:
Error
09:53:10,775 WARN [org.hibernate.engine.jdbc.spi.SqlExceptionHelper]
(default task-33) SQL Error: 1062, SQLState: 23000 09:53:10,781 ERROR
[org.hibernate.engine.jdbc.spi.SqlExceptionHelper] (default task-33)
Duplicate entry '384-8576' for key 'PRIMARY' 09:53:10,781 INFO
[org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl] (default
task-33) HHH000010: On release of batch it still contained JDBC
statements 09:53:10,782 ERROR
[org.hibernate.internal.ExceptionMapperStandardImpl] (default task-33)
HHH000346: Error during managed flush
[org.hibernate.exception.ConstraintViolationException: could not
execute statement]
Question
How would I implement the code in order to allow UPDATES if the row exists?
Thank you
More info
Here is the Entity concerned:
Person.java
#Entity
#Table(name = "person")
#XmlRootElement(name = "person")
public class Person extends AbstractDomain<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Size(min = 1, max = 45)
#Column(name = "UID")
private String uid;
#Column(name = "JOINING_DATE", nullable = false)
private Long joiningDate;
#Column(name = "LASTACCESS_DATE", nullable = false)
private Long lastAccessDate;
#Size(min = 1, max = 85)
#Column(name = "DISPLAY_NAME", nullable = false)
private String displayName;
#Size(min = 5, max = 55)
#Column(name = "EMAIL_ADDRESS", nullable = false, unique = true)
private String emailAddress;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "person_language", joinColumns = {
#JoinColumn(name = "PER_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
#JoinColumn(name = "LANG_ID", referencedColumnName = "LANGUAGE_CODE", unique = true) })
private Set<Language> languages;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinTable(name = "person_location", joinColumns = {
#JoinColumn(name = "PER_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
#JoinColumn(name = "LOC_ID", referencedColumnName = "ID", unique = true) })
private Set<LocationPerson> locations = null;
#Basic(fetch = FetchType.LAZY)
#Lob
#Column(name = "AVATAR", nullable = true)
private byte[] avatar;
#Column(name = "PROVIDER")
private Integer provider;
#Column(name = "AVATAR_FIREBASE")
private String avatarFirebase;
#Column(name = "MILES_KM")
private Integer milesKm;
#Column(name = "NOTIFICATIONS")
private Integer notifications;
#Size(max = 256)
#Column(name = "DEVICE_TOKEN")
private String deviceToken;
#XmlElement
public String getDeviceToken() {
return deviceToken;
}
public void setDeviceToken(String deviceToken) {
this.deviceToken = deviceToken;
}
#XmlElement
public Integer getMilesKm() {
return milesKm;
}
public void setMilesKm(Integer milesKm) {
this.milesKm = milesKm;
}
#Column(name = "CONTACTABLE")
private Long contactable;
#Transient
private Boolean contactableFree;
#XmlElement
public Boolean getContactableFree() {
return contactableFree;
}
public void setContactableFree(Boolean contactableFree) {
this.contactableFree = contactableFree;
}
#XmlElement
public Long getContactable() {
return contactable;
}
public void setContactable(Long contactable) {
this.contactable = contactable;
}
#XmlElement
public Integer getNotifications() {
return notifications;
}
public void setNotifications(Integer notifications) {
this.notifications = notifications;
}
#XmlElement
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#XmlElement
public Long getJoiningDate() {
return joiningDate;
}
public void setJoiningDate(Long joiningDate) {
this.joiningDate = joiningDate;
}
#XmlElement
public Long getLastAccessDate() {
return lastAccessDate;
}
public void setLastAccessDate(Long lastAccessDate) {
this.lastAccessDate = lastAccessDate;
}
#XmlElement
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
#XmlElement
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
#XmlElement
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
#XmlElement
public Set<Language> getLanguages() {
return languages;
}
public void setLanguages(Set<Language> languages) {
this.languages = languages;
}
#XmlElement
public Set<LocationPerson> getLocations() {
return locations;
}
public void setLocations(Set<LocationPerson> locations) {
if (this.locations == null) {
this.locations = locations;
} else {
this.locations.retainAll(locations);
this.locations.addAll(locations);
}
}
public void putLocations(Set<LocationPerson> locations) {
if (locations != null) {
this.locations.clear();
this.locations.addAll(locations);
} else {
this.locations = locations;
}
}
#XmlElement
public byte[] getAvatar() {
return avatar;
}
public void setAvatar(byte[] avatar) {
this.avatar = avatar;
}
#XmlElement
public Integer getProvider() {
return provider;
}
public void setProvider(Integer provider) {
this.provider = provider;
}
#XmlElement
public String getAvatarFirebase() {
return avatarFirebase;
}
public void setAvatarFirebase(String avatarFirebase) {
this.avatarFirebase = avatarFirebase;
}
}
UPDATE
After reading the following, I change the Person object locations from CascadeType.ALL to CascadeType.PERSIST.
Person.java
#OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinTable(name = "person_location", joinColumns = {
#JoinColumn(name = "PER_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
#JoinColumn(name = "LOC_ID", referencedColumnName = "ID", unique = true) })
private Set<LocationPerson> locations = null;
The update is done to the database, but I now get the following:
10:41:20,657 ERROR
[org.hibernate.internal.ExceptionMapperStandardImpl] (default task-7)
HHH000346: Error during managed flush
[org.hibernate.TransientObjectException: object references an unsaved
transient instance - save the transient instance beforeQuery flushing:
com.jobs.spring.domain.LocationPerson] 10:41:21,429 INFO
[org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl] (default
task-7) HHH000010: On release of batch it still contained JDBC
statements
I have fetched a list of objects of type Company from the database. But when trying to serialize the list using Jackson to JSON, throws an error
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.zerosolutions.repository.entity.CompanySector.companies, could not initialize proxy - no Session (through reference chain: java.util.ArrayList[0]->com.zerosolutions.repository.entity.Company["sector"]->com.zerosolutions.repository.entity.CompanySector["companies"])
Company:
#Entity
#Table(name = "company")
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "name")
private String name;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "sector")
private CompanySector sector;
#OneToOne
#JoinColumn(name = "id")
private CompanyProfile profile;
#OneToOne
#JoinColumn(name = "id")
private CompanyAddress address;
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private List<Job> jobs = new ArrayList<>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CompanySector getSector() {
return sector;
}
public void setSector(CompanySector sector) {
this.sector = sector;
}
public CompanyProfile getProfile() {
return profile;
}
public void setProfile(CompanyProfile profile) {
this.profile = profile;
}
public CompanyAddress getAddress() {
return address;
}
public void setAddress(CompanyAddress address) {
this.address = address;
}
public List<Job> getJobs() {
return jobs;
}
public void setJobs(List<Job> jobs) {
this.jobs = jobs;
}
}
CompanySector:
#Entity
#Table(name = "sectors")
public class CompanySector {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="sector")
private String sector;
#OneToMany(mappedBy="sector", cascade=CascadeType.PERSIST)
private List<Company> companies = new ArrayList<>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getSector() {
return sector;
}
public void setSector(String sector) {
this.sector = sector;
}
public List<Company> getCompanies() {
return companies;
}
}
Conversion code:
#RequestMapping(value = "/getCompanyList", method = RequestMethod.GET, produces={ "application/json"})
public #ResponseBody String getCompanyList() {
ObjectMapper mapper = new ObjectMapper();
try {
List<Company> companyList = companyServices.getCompanyList();
String companyListString = mapper.writeValueAsString(companyList); // this line throws error
return companyListString;
} catch (JsonProcessingException e) {
logger.error(e);
return null;
}
}
Any Suggestions what might be wrong here ?
Fetching list:
public List<Company> getCompanyList(){
Session session = sessionFactory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
List<Company> companies = session.createCriteria(Company.class).list();
logger.debug(companies);
tx.commit();
System.out.println("companies fetched");
return companies;
} catch(Exception e){
logger.error("Exceptino thrown: " + e);
tx.rollback();
return null;
} finally{
session.close();
}
}
If you know that you'll want to see all Companies every time you retrieve a CompanySector then change your field mapping for Companies to:
#OneToMany(fetch = FetchType.EAGER, mappedBy="sector", cascade=CascadeType.PERSIST)
private List<Company> companies = new ArrayList<>();
Another approach use Hibernate.initialize :
Hibernate.initialize(companySector.getCompany());
Also see this link it's very helpful
Please help me in accessing Employee object in the below code using JAXB annotations. The application was developed in JPA SPRING. We are unable to access sub-object properties i.e, Employee properties
RESOURCE CORE FILE
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement(name="resource")
#Entity
#Table(name = "resource")
public class Resource implements java.io.Serializable {
private Integer resourceId;
private String resourceCode;
private String resourceName;
private String resourceNumber;
private Employee employee;
public Resource() {
}
public Resource(Employee employee,String resourceCode, String resourceName,
String resourceNumber
) {
this.employee = employee;
this.resourceCode = resourceCode;
this.resourceName = resourceName;
this.resourceNumber = resourceNumber;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "resource_id", unique = true, nullable = false)
public Integer getResourceId() {
return this.resourceId;
}
public void setResourceId(Integer resourceId) {
this.resourceId = resourceId;
}
#Column(name = "resource_code")
public String getResourceCode() {
return this.resourceCode;
}
public void setResourceCode(String resourceCode) {
this.resourceCode = resourceCode;
}
#Column(name = "resource_number")
public String getResourceNumber() {
return this.resourceNumber;
}
public void setResourceNumber(String resourceNumber) {
this.resourceNumber = resourceNumber;
}
#Column(name = "resource_name")
public String getResourceName() {
return this.resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employee_id")
public Employee getEmployee() {
return this.employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}
EMPLOYEE CORE FILE
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement(name="employee")
#Entity
#Table(name = "employee")
public class Employee implements java.io.Serializable {
private Integer employeeId;
private String employeeCode;
private String employeeName;
private List<Resource> resources = new ArrayList<Resource>(0);
public Employee() {
}
public Employee(String employeeCode, String employeeName,List<Resource> resources
) {
this.employeeCode = employeeCode;
this.employeeName = employeeName;
this.resources = resources;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "employee_id", unique = true, nullable = false)
public Integer getEmployeeId() {
return this.employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
#Column(name = "employee_code")
public String getEmployeeCode() {
return this.employeeCode;
}
public void setEmployeeCode(String employeeCode) {
this.employeeCode = employeeCode;
}
#Column(name = "employee_name")
public String getEmployeeName() {
return this.employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employee")
public List<Resource> getResources() {
return this.resources;
}
public void setResources(List<Resource> resources) {
this.resources = resources;
}
}
You have to use the FetchType : Eager in RESOURCE CORE FILE of getEmployee() Method. Lazy fetch type is pulling only the parent object. Eager is pulling both.