I have problem with updating a database record using EJB and JPA. Persistence provider: org.eclipse.persistence.jpa.PersistenceProvider
When I am creating a record I am using this method:
public void create(T entity) {
getEntityManager().persist(entity);
}
All is ok. Now I want to edit the the same record. For e.g. I have an entity:
#Entity
#Table(name = "OPERATION")
public class Operation implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "OPERATION_AUTHOR")
private String operationAuthor;
#Column(name = "OPERATION_TYPE")
private String operationType;
#Column(name = "OPERATION_STATUS")
private String operationStatus;
#Column(name = "CREATED")
#Temporal(value = TemporalType.DATE)
private Date created;
#Column(name = "COMPLETED")
#Temporal(value = TemporalType.DATE)
private Date completed;
//Getters and setters
}
For e.g. I want to update only operationStatus. I am creating an entity, setting to it the same record id and new operationStatus. For updating I am using this method:
public void edit(T entity) {
getEntityManager().merge(entity);
}
The problem is when I update the record the status is updated correctly but all the other columns' values are set to null not left as they were before. I want to update only operationStatus and left other values untouched. Is this possible to do this using EJB? And what should I change to make this happen?
Load the entity first, using Operation op = getEntityManager().find(Operation.class,id). Then do the op.setOperationStatus(value). That's all, it will get updated on session flush/close.
The issue is that calling entityManager.merge(entity) will update the db row (corresponding to the ID you set on the object) with the same exact values that populate the java object (i.e., everything except status and id are null). You need to get the object out of the database and then update it.
A better option for this may be the following method:
/**
*
* #param id - the ID of the entity to update
* #param operationStatus - the status to apply
*/
public void setOperationStatus(long id, String operationStatus){
Operation o = getEntityManager().find(Operation.class,id);
o.setOperationStatus(operationStatus);
getEntityManager().merge(entity);
}
Related
This is a follow-up question to my previous one How to model packages, versions and licenses?.
Here is my database setup.
V1__create_table_license.sql
CREATE TABLE IF NOT EXISTS license (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
reference TEXT NOT NULL,
is_deprecated_license_id BOOLEAN NOT NULL,
reference_number INTEGER NOT NULL,
license_id TEXT NOT NULL,
is_osi_approved BOOLEAN NOT NULL
);
INSERT INTO license
("name",reference,is_deprecated_license_id,reference_number,license_id,is_osi_approved)
VALUES
('MIT License','./MIT.json',false,275,'MIT',true);
V2__create_npm_package.sql
CREATE TABLE IF NOT EXISTS npm_package (
id BIGSERIAL PRIMARY KEY,
name TEXT NOT NULL,
description TEXT NOT NULL
);
INSERT INTO npm_package
(name, description)
VALUES
('react', 'React is a JavaScript library for building user interfaces.'),
('react-router-dom', 'DOM bindings for React Router'),
('typescript', 'TypeScript is a language for application scale JavaScript development'),
('react-dom', 'React package for working with the DOM.');
V3__create_npm_version.sql
CREATE TABLE IF NOT EXISTS npm_package_version (
npm_package_id BIGINT NOT NULL REFERENCES npm_package,
version TEXT NOT NULL,
license_id INTEGER NOT NULL REFERENCES license,
UNIQUE(npm_package_id, version)
)
Here are my Java objects.
License.java
#Entity
public class License {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String reference;
private Boolean isDeprecatedLicenseId;
private Integer referenceNumber;
private String name;
private String licenseId;
private Boolean isOsiApproved;
}
LicenseRepository.java
public interface LicenseRepository extends JpaRepository<License, Integer> {
License findByLicenseIdIgnoreCase(String licenseId);
}
NpmPackage.java
#Entity
public class NpmPackage {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#OneToMany(mappedBy = "npmPackage", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NpmPackageVersion> versions = new ArrayList<>();
public NpmPackage() {}
public void addVersion(NpmPackageVersion version) {
this.versions.add(version);
version.setNpmPackage(this);
}
public void removeVersion(NpmPackageVersion version) {
this.versions.remove(version);
version.setNpmPackage(null);
}
}
#Entity
public class NpmPackageVersion {
public NpmPackageVersion() {}
public NpmPackageVersion(String version, License license) {
this.setVersion(version);
this.license = license;
}
#EmbeddedId private NpmPackageIdVersion npmPackageIdVersion = new NpmPackageIdVersion();
#MapsId("npmPackageId")
#ManyToOne(fetch = FetchType.LAZY)
private NpmPackage npmPackage;
#ManyToOne(fetch = FetchType.LAZY)
private License license;
#Embeddable
public static class NpmPackageIdVersion implements Serializable {
private static final long serialVersionUID = 3357194191099820556L;
private Long npmPackageId;
private String version;
// ...
}
public String getVersion() {
return this.npmPackageIdVersion.version;
}
public void setVersion(String version) {
this.npmPackageIdVersion.version = version;
}
}
MyRunner.java
#Component
class MyRunner implements CommandLineRunner {
#Autowired LicenseRepository licenseRepository;
#Autowired NpmPackageRepository npmPackageRepository;
#Override
// #Transactional
public void run(String... args) throws Exception {
// get license from database
var license = licenseRepository.findByLicenseIdIgnoreCase("mit");
// get package from db
var dbPackage = npmPackageRepository.findByNameIgnoreCase("react");
var version = new NpmPackageVersion("1.0.0", license);
dbPackage.addVersion(version);
npmPackageRepository.save(dbPackage);
}
}
In my previous question I got the answer to use fetch = FetchType.EAGER but then I learned that this is not ideal. I'd like to use lazy fetching.
#OneToMany(mappedBy = "npmPackage", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<NpmPackageVersion> versions = new ArrayList<>();
So I removed the eager fetching and run into an error.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.bom.NpmPackage.NpmPackage.versions, could not initialize proxy - no Session
With the #Transactional annotation everything works. Why is this the case? I tried to read everything online but I still don't really get it. I understand that the database session is closed at some point and I wonder where exactly this is the case. I also wonder if I could do something about, e.g. I tried to fetch all versions to ensure they are loaded before I add another one.
So do I really have to use #Transactional or is there another solution? I just want to understand the "magic" going on :)
Thank you very much!
When you use FetchType.LAZY, Hibernate ORM doesn't really return an initialized collection when you find the entity. The association is going to be a proxy and when you need access to the collection, Hibernate ORM is going to query the database and get it.
To achieve this, the entity (the NpmPackage) needs to be in a managed state. If the entity is not managed and you try to access a lazy association (versions in this case), you get the LazyInitializationException.
In your example, when you use #Transactional, the entity stays managed for the duration of the method. Without it, it becomes not managed as soon as you return from findByNameIgnoreCase.
If you know that you will need the association versions, you could also use a fetch join query to get the NpmPackage:
from NpmPackage p left join fetch p.versions where p.name=:name
This way the associations stays lazy but you can get it with a single query.
I am using spring-data-jpa to read data from db.
JPA Entity
#Entity
#Table(name = "ICM_STATUSES")
public class IcmMdStatuses implements Serializable {
#Id
#Column(name = "STATUS_INTERNAL_IDENTIFIER")
private long statusInternalIdentifier;
#Column(name = "STATUS_IDENTIFIER")
private String statusIdentifier;
#Column(name = "STATUS_NAME")
private String statusName;
#Column(name = "STATE_NAME")
private String stateName;
#Column(name = "IS_ACTIVE")
private String isActive= "Y";
#Column(name = "CREATED_BY")
private String createdBy;
#CreationTimestamp
#Column(name = "CREATE_DATE")
private Date createdDate;
#Column(name = "UPDATED_BY")
private String updatedBy;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "UPDATE_DATE")
private Date updateDate;
}
JPA Repository
public interface StatusRepository extends JpaRepository<IcmMdStatuses, Long> {
Optional<IcmMdStatuses> findByStatusIdentifier(String statusIdentifier);
}
DB Table Data
Problem:
If I try to fetch Open status record. I am getting Hibernate Proxy Object instead of real object.
If I try to fetch Reopen status record. i am getting real object.
Except for Open status, for the rest all statuses i am getting real objects.
I could able to get the real object using Hibernate.unproxy(-)
Problem
MyEntity entity = new MyEntity();
//If i use any other status except Open the below line saves pk of that particular status.
entity.setFromStatus(statusRepository.findByStatusIdentifier("Open"));//saving null in db
entity.setToStatus(statusRepository.findByStatusIdentifier("Reopen"));//saving value 93(PK of Reopen status)
myRepo.save(entity);
Please help to understand what's the actual issue, as i am experiencing this issue only for Open Status, rest all works as expected.
The problem is most probably due to open-session-in-view and the fact that you loaded some other object that refers to this Open status object. Since that other object did not load the Open status object, it created a proxy for it which is then part of the persistence context. The next time you load that object, you retrieve that proxy instead of the real object because Hibernate must maintain the object identity guarantee for a persistence context.
I'm using hibernate-types-52 by Vlad Mihalcea together with Spring JPA to insert a POJO as a Json value into my Postgresql database.
My entity is defined this way:
#Entity
#Table(name = "hoshin_kanri")
#TypeDef(
name = "jsonb",
typeClass = JsonBinaryType.class
)
public class HKEntity {
#Id
#Column(name = "id_ai", columnDefinition = "bigint")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id_ai;
#Column(name = "id_hk", columnDefinition = "bigint")
private Integer id_hk;
#Type(type = "jsonb")
#Column(name = "hk_data", columnDefinition = "jsonb")
private HKData hk_data;
public HKEntity(Integer id_hk, HKData hk_data) {
this.id_hk = id_hk;
this.hk_data = hk_data;
}
And this is the POJO:
public class HKData {
private String name;
private Year targetYear;
private String description;
public HKData(String name, Year targetYear, String description) {
this.name = name;
this.targetYear = targetYear;
this.description = description;
}
I've defined a Repository interface to query the objects into the database:
public interface HKRepository extends CrudRepository<HKEntity, Integer> {
#Query(value = "INSERT INTO 'hk_data' VALUES :Entity", nativeQuery = true)
void test_json(#Param("Entity") HKEntity e);
}
and a test Service just to see if it's working properly:
#Service
public class HKService {
#Autowired
HKRepository hk_repository;
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.test_json(e);
return "Value created";
}
}
However, i keep getting the following exception:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.ehk.rest.entity.HKEntity
I've tried many fixes suggested for this error, but i cannot understand the nature of the error itself. What is wrong with this approach? Beside a tip for fixing this, i would like to understand why this error is originated.
The error means that there's an instance of the HKEntity entity which is referenced from somewhere in the current Hibernate session, and you've neither explicitly persisted this instance, nor instructed Hibernate to persist it cascadly. It's hard to say what exactly is going on, but there are some issues with your code that might have confused either Spring Data JPA framework, or the Hibernate itself.
First, the Spring's CrudRepository interface already has a save() method, so you could use it instead of your test_json() method.
I also see no reason in inserting a Hibernate entity with a native query, and I don't even think this is a valid query. Your test_json() method tries to natively insert an HKEntity entity into the hk_data table, but the HKEntity entity should be saved into the hoshin_kanri table, according to your mapping.
So I would change your service code as follows:
public String json_test() {
HKData d = new HKData("Prova", Year.now(), "Descrizione");
HKEntity e = new HKEntity(1,d);
hk_repository.save(e);
return "Value created";
}
My scenario is very simple. I have an entityID identity field in the #Entity class and the DB (Oracle, which I'm not sure that matters):
#Id
#SequenceGenerator(name="ENTITY_SEQ_GEN", sequenceName="SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ENTITY_SEQ_GEN")
#Column(name="ENTITY_ID")
private long entityID;
I have another field called, let's say, entityReadableID and that should be a String consisting of the stringified entityID concatenated with another String field from the entity. E.g. if entityID is 1234, entityReadableID may be something like 1234ABC.
My problem is that, as far as I know, the value of entityID is not known before the row is created in the DB but I need to concatenate the entityReadableID using its value. Is there a way to fetch the value of the sequence generated ID before the row is created in the DB so that I can use it to generate the other ID? I know I can make it an insert with that field being null and then make an update once I know what entityID is but that solution seems less than elegant.
The way I am hoping Hibernate/Oracle may be able to support this is if Hibernate can somehow "reserve/issue" the next generated value for the entity being processed before the actual persistence, let me know what it is so I can manipulate with it, then at the end persist it.
You can get the generated Id before persisting of that entity by not using sequence directly for that entity, I mean use a separated entity for that sequence, so your entity should be something like:
#Entity
#Table(name = "ENTITY"
)
public class EntityClass implements java.io.Serializable {
private Long entityId;
private String entityRelatedId;
public EntityClass() {
}
// you may have other constructors
#Id
#Column(name = "ENTITY_ID", nullable = false)
public Long getEntityId() {
return this.entityId;
}
public void setEntityId(Long entityId) {
this.entityId = entityId;
}
#Column(name = "ENTITY_RELATED_ID", length = 50)
public String getEntityRelatedId() {
return this.entityRelatedId;
}
public void setEntityRelatedId(String entityRelatedId) {
this.entityRelatedId = entityRelatedId;
}
}
and the entity for the sequence is something like:
#Entity
#Table(name = "SEQ_GENERIC_TBL"
)
public class SeqGenericTbl implements java.io.Serializable {
private Long id;
public SeqGenericTbl() {
}
public SeqGenericTbl(Long id) {
this.id = id;
}
#Id
#SequenceGenerator(name = "ENTITY_SEQ_GEN",
sequenceName = "SEQ_GENERIC", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ENTITY_SEQ_GEN")
#Column(name = "ID")
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
}
Now you can get the Id from the entity of the sequence (persist it, you can rollback that tran or delete it or empty the related table later):
SessionFactory sf = HBUtil.getSessionFactory();
Session s = sf.openSession();
SeqGenericTbl sg=new SeqGenericTbl();
s.save(sg);
EntityClass entity1 = new EntityClass();
entity1.setEntityId(sg.getId());
//NOW YOU HAVE THE ID WITHOUT PERSISTING THE ENTITY
System.out.println(entity1.getEntityId());
I have a domain object and annotated as follows
#Entity
#Table(name = "REQUEST")
public class Request {
/**
* Unique id for this request
*/
#Id
#GeneratedValue
#Column(name = "EQ_ID")
private long requestId;
/**
*
*/
#Column(name = "EMAIL_ID")
private String emailId;
/**
*
*/
#Column(name = "REQUEST_DATE")
private Date requestDate;
/**
*Getters/setters omitted
*/
}
The column Request_date cannot be null and as per the DDL the default value is sysdate (oracle DB). How do I annotate this field so that if the requestDate property is null,hiberanate automatically inserts sysdate.? Currently it throws error when the field is null,which is very obvious as it cannot be null as per the DB constraints. How do I go about this?
One alternative is to mark this field as transient and the inserts work fine. But the negative aspect is that, I will not be able to retrieve the value (of request_date column).
This is a missing feature in hibernate annotations. Also there exist some workaround as Yok has posted. The problem is that the workaround is vendor dependent and might not work for all DB. In my case,Oracle, it isn't working and has been reported as a bug.
You can put the default value in a columnDefinition. An example would look like:
#Column(name = "REQUEST_DATE", nullable = false, columnDefinition = "date default sysdate")
Using #ColumnDefault (Work for DDL update).
hibernate.hbm2ddl.auto=update
import org.hibernate.annotations.ColumnDefault;
....
#ColumnDefault(value="'#'")
#Column(name = "TEMP_COLUMN", nullable = false)
public String getTempColumn() {
return tempColumn;
}
DDL Generate:
Alter Table YOUR_TABLE add TEMP_COLUMN varchar2(255) default '#' not null;
Assign a default value to the field:
private Date requestDate = new Date();
If you mark your entity with #DynamicInsert e.g.
#Entity
#DynamicInsert
#Table(name = "TABLE_NAME")
public class ClassName implements Serializable {
Hibernate will generate SQL without null values. Then the database will insert its own default value. This does have performance implications See Dynamic Insert.
Make the default in Oracle for the column SYSDATE:
ALTER TABLE APP MODIFY (REQUEST_DATE DEFAULT SYSDATE);
Then, from Hibernate's perspective it can be nullable.
Hibernate will save a NULL to the database. Oracle will convert that to SYSDATE. And everyone will be happy.
I resolved assigning a value to the variable like this private Integer active= 0;
#Entity
#Table(name="products")
public class ServiziTipologia {
#Id
#GeneratedValue
private Integer id;
private String product;
private String description;
private Integer active= 0;