I am using envers to audit my entities. My code looks somewhat like this
#Audited( targetAuditMode = RelationTargetAuditMode.NOT_AUDITED )
#AuditOverride( forClass = Task.class, isAudited = true )
public class Job extends Task
{...}
#Inheritance( strategy = InheritanceType.JOINED )
#Audited( targetAuditMode = RelationTargetAuditMode.NOT_AUDITED )
public class Task
{
...
#ManyToOne( fetch = FetchType.LAZY )
#LazyToOne( value = LazyToOneOption.NO_PROXY )
#Fetch( value = FetchMode.SELECT )
#JoinColumn( nam = "id_util" )
#Audited( targetAuditMode = RelationTargetAuditMode.AUDITED )
private Utility utility;
}
#Entity
#DynamicInsert
#DynamicUpdate
#Audited( targetAuditMode = RelationTargetAuditMode.NOT_AUDITED )
public class Utility
{
#Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append( this.getClass().getName() ).append( "#" ).append( getId() );
builder.append( "[" );
appendAttributeValues( builder );
builder.append( "]" );
return builder.toString();
}
public Long getId()
{
return id;
}
}
When I try to fetch the revisions of a certain job entity, the field utility is not loaded correctly. Instead, hibernate gives a
Method threw 'org.hibernate.exception.GenericJDBCException' exception. Cannot evaluate Utility$HibernateProxy$9GVDBIUC.toString()
The rest of the entity revisions which consists of strings and numbers is loaded just fine. I also don't get this error when auditing and querying other entities which do not have an inheritance structure.
The _aud tables for the entities Job, Task and Utility are all filled correctly. What might be causing this error?
Before using linked object you should "materialize" proxy-object created by hibernate, like sayed in this answer: https://stackoverflow.com/a/2216603/3744622
Hibernate.initialize(entity);
if (entity instanceof HibernateProxy) {
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
.getImplementation();
}
return entity;
or, for instance, using special method unproxy:
Object unproxiedObject = Hibernate.unproxy(proxiedObject);
TLDR The reason is inconsistent audit data. It happens when you you start auditing already existing entities and their relations.
Solution Copy all entity data in the corresonding aud table with rev = 1,revtype = 0. Insert rev=1, revtstmp=0 into revinfo table.
See also Safe Envers queries when the audit history is incomplete
Explanation Because the entity data existed before envers is integrated, the audit tables are initially empty. When you update an entity A whichs owns a reference to entity B, an audit records is created in A_aud but not in B_aud. When A is queried for revisions it tries to look up the referred entity B in the emtpy B_aud and yields an EntityNotFoundException, which got wrapped up in the given GenericJDBCException.
Related
I am trying to store a JSON object in MySQL database in spring boot. I know I am doing something wrong but I a can't figure out what it is because I am fairly new to Spring.
I have a rest endpoint where I get the following JSON object (via HTTP PUT) and I need to store it in database so that the user can fetch it later (via HTTP GET).
{
"A": {
"Name": "Cat",
"Age": "1"
},
"B": {
"Name": "Dog",
"Age": "2"
},
"C": {
"Name": "Horse",
"Age": "1"
}
}
Note that in the above case The number of keys in the object may vary, Due to that requirement I am using a HashMap to catch the object in the controller.
#RequestMapping(method = RequestMethod.POST)
public String addPostCollection(#RequestBody HashMap<String, Animal> hp) {
hp.forEach((x, y) -> {
postRepository.save(hp.get(x));
});
return "OK";
}
As you can see in the method, I can iterate the HashMap and persist each Animal object in db. But I am looking for a way to persist the entire HashMap in a single record. I have did some reading and they suggest me to use a #ManyToMany mapping.
Can anyone point me in a direction to persist the HashMap in a different way? (or is using the #ManyToMany the only and right way to do this?)
Maven dependency
The first thing you need to do is to set up the following Hibernate Types Maven dependency in your project pom.xml configuration file:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
Domain model
Let's assume you have the following entity:
#Entity(name = "Book")
#Table(name = "book")
#TypeDef(
typeClass = JsonType.class,
defaultForType = JsonNode.class
)
public class Book {
#Id
#GeneratedValue
private Long id;
#NaturalId
private String isbn;
#Column(columnDefinition = "jsonb")
private JsonNode properties;
//Getters and setters omitted for brevity
}
Notice the #TypeDef is used to instruct Hibernate to map the JsonNode object using the JsonType offered by the Hibernate Types project.
Testing time
Now, if you save an entity:
Book book = new Book();
book.setIsbn( "978-9730228236" );
book.setProperties(
JacksonUtil.toJsonNode(
"{" +
" \"title\": \"High-Performance Java Persistence\"," +
" \"author\": \"Vlad Mihalcea\"," +
" \"publisher\": \"Amazon\"," +
" \"price\": 44.99" +
"}"
)
);
entityManager.persist( book );
Hibernate is going to generate the following SQL statement:
INSERT INTO
book
(
isbn,
properties,
id
)
VALUES
(
'978-9730228236',
'{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',
1
)
And you can also load it back and modify it:
Session session = entityManager.unwrap( Session.class );
Book book = session
.bySimpleNaturalId( Book.class )
.load( "978-9730228236" );
LOGGER.info( "Book details: {}", book.getProperties() );
book.setProperties(
JacksonUtil.toJsonNode(
"{" +
" \"title\": \"High-Performance Java Persistence\"," +
" \"author\": \"Vlad Mihalcea\"," +
" \"publisher\": \"Amazon\"," +
" \"price\": 44.99," +
" \"url\": \"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" +
"}"
)
);
Hibernate taking caare of the UPDATE statement for you:
SELECT b.id AS id1_0_
FROM book b
WHERE b.isbn = '978-9730228236'
SELECT b.id AS id1_0_0_ ,
b.isbn AS isbn2_0_0_ ,
b.properties AS properti3_0_0_
FROM book b
WHERE b.id = 1
-- Book details: {"price":44.99,"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon"}
UPDATE
book
SET
properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":"https://www.amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/"}'
WHERE
id = 1
You can use FasterXML (or similar) to parse the Json into an actual object (you need to define the class) and use Json.toJson(yourObj).toString() to retrieve the Json String. It also simplifies working with the objects since your data class may also have functionality.
Your JSON is well structered, so usually theres no need to persist the entire map in one single record. You won't be able to use the Hibernate/JPA query functions and a lot more.
If you really want to persist the entire map in one single record, you could persist the map in its string representation and, as already proposed, use a JSON parser like Jackson to rebuild your HashMap
#Entity
public class Animals {
private String animalsString;
public void setAnimalsString(String val) {
this.animalsString = val;
}
public String getAnimalsString() {
return this.animalsMap;
}
public HashMap<String, Animal> getAnimalsMap() {
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String,Animal>> typeRef = new TypeReference<HashMap<String,Animal>>() {};
return mapper.readValue(animalsString, typeRef);
}
}
Your animal class:
public class Animal {
private String name;
private int age;
/* getter and setter */
/* ... */
}
And you could change your controller method to
#RequestMapping(method = RequestMethod.POST)
public String addPostCollection(#RequestBody String hp) {
Animals animals = new Animals();
animals.setAnimalsString(hp);
animalsRepository.save(hp);
return "OK";
}
One animal is one record. You are saving more records, not one record. You can commit more records in one transaction.
See: How to persist a lot of entities (JPA)
Is there a way for updating only some fields of an entity object using the method save from Spring Data JPA?
For example I have a JPA entity like this:
#Entity
public class User {
#Id
private Long id;
#NotNull
private String login;
#Id
private String name;
// getter / setter
// ...
}
With its CRUD repo:
public interface UserRepository extends CrudRepository<User, Long> { }
In Spring MVC I have a controller that get an User object for update it:
#RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<?> updateUser(#RequestBody User user) {
// Assuming that user have its id and it is already stored in the database,
// and user.login is null since I don't want to change it,
// while user.name have the new value
// I would update only its name while the login value should keep the value
// in the database
userRepository.save(user);
// ...
}
I know that I could load the user using findOne, then change its name and update it using save... But if I have 100 fields and I want to update 50 of them it could be very annoying change each value..
Is there no way to tell something like "skip all null values when save the object"?
I had the same question and as M. Deinum points out, the answer is no, you can't use save. The main problem being that Spring Data wouldn't know what to do with nulls. Is the null value not set or is it set because it needs to be deleted?
Now judging from you question, I assume you also had the same thought that I had, which was that save would allow me to avoid manually setting all the changed values.
So is it possible to avoid all the manuel mapping then? Well, if you choose to adhere to the convention that nulls always means 'not set' and you have the original model id, then yes.
You can avoid any mapping yourself by using Springs BeanUtils.
You could do the following:
Read the existing object
Use BeanUtils to copy values
Save the object
Now, Spring's BeanUtils actual doesn't support not copying null values, so it will overwrite any values not set with null on the exiting model object. Luckily, there is a solution here:
How to ignore null values using springframework BeanUtils copyProperties?
So putting it all together you would end up with something like this
#RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public ResponseEntity<?> updateUser(#RequestBody User user) {
User existing = userRepository.read(user.getId());
copyNonNullProperties(user, existing);
userRepository.save(existing);
// ...
}
public static void copyNonNullProperties(Object src, Object target) {
BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
}
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for(java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
Using JPA you can do it this way.
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaUpdate<User> criteria = builder.createCriteriaUpdate(User.class);
Root<User> root = criteria.from(User.class);
criteria.set(root.get("lastSeen"), date);
criteria.where(builder.equal(root.get("id"), user.getId()));
session.createQuery(criteria).executeUpdate();
You are able to write something like
#Modifying
#Query("update StudentXGroup iSxG set iSxG.deleteStatute = 1 where iSxG.groupId = ?1")
Integer deleteStudnetsFromDeltedGroup(Integer groupId);
Or If you want to update only the fields that were modified you can use annotation
#DynamicUpdate
Code example:
#Entity
#Table(name = "lesson", schema = "oma")
#Where(clause = "delete_statute = 0")
#DynamicUpdate
#SQLDelete(sql = "update oma.lesson set delete_statute = 1, "
+ "delete_date = CURRENT_TIMESTAMP, "
+ "delete_user = '#currentUser' "
+ "where lesson_id = ?")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
If you are reading request as JSON String, this could be done using Jackson API. Here is code below. Code compares an existing POJO Elements and create new one with updated fields. Use the new POJO to persist.
public class TestJacksonUpdate {
class Person implements Serializable {
private static final long serialVersionUID = -7207591780123645266L;
public String code = "1000";
public String firstNm = "John";
public String lastNm;
public Integer age;
public String comments = "Old Comments";
#Override
public String toString() {
return "Person [code=" + code + ", firstNm=" + firstNm + ", lastNm=" + lastNm + ", age=" + age
+ ", comments=" + comments + "]";
}
}
public static void main(String[] args) throws JsonProcessingException, IOException {
TestJacksonUpdate o = new TestJacksonUpdate();
String input = "{\"code\":\"1000\",\"lastNm\":\"Smith\",\"comments\":\"Jackson Update WOW\"}";
Person persist = o.new Person();
System.out.println("persist: " + persist);
ObjectMapper mapper = new ObjectMapper();
Person finalPerson = mapper.readerForUpdating(persist).readValue(input);
System.out.println("Final: " + finalPerson);
}}
Final output would be, Notice only lastNm and Comments are reflecting changes.
persist: Person [code=1000, firstNm=John, lastNm=null, age=null, comments=Old Comments]
Final: Person [code=1000, firstNm=John, lastNm=Smith, age=null, comments=Jackson Update WOW]
skip all null values when save the object
As others have pointed out, there is not straight forward solution in JPA.
But thinking out of the box you can use MapStruct for that.
This means you use the right find() method to get the object to update from the DB, overwrite only the non-null properties and then save the object.
You can use JPA as you know and just use MapStruct like this in Spring to update only the non-null properties of the object from the DB:
#Mapper(componentModel = "spring")
public interface HolidayDTOMapper {
/**
* Null values in the fields of the DTO will not be set as null in the target. They will be ignored instead.
*
* #return The target Holiday object
*/
#BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
Holiday updateWithNullAsNoChange(HolidayDTO holidayDTO, #MappingTarget Holiday holiday);
}
See the MapStruct docu on that for details.
You can inject the HolidayDTOMapper the same way you do it with other beans (#Autowired, Lombok,...) .
the problem is not spring data jpa related but the jpa implementation lib that you are using related.
In case of hibernate you may have a look at:
http://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/
For long,int and other types;
you can use the following code;
if (srcValue == null|(src.getPropertyTypeDescriptor(pd.getName()).getType().equals(long.class) && srcValue.toString().equals("0")))
emptyNames.add(pd.getName());
We are currently facing an 'issue' with new entities and the findDirty interceptor - and from our perspective it looks like a bug. However it might be also a feature - thus expertise and comments on this topic is appreciated.
Cheers
Christoph
Our environment:
Hibernate 4.2.7 (using JPA API 2.x)
The issue:
We have two entities UserBO and InternalOrganizationBO
The relationsship between the two objects is shown below.
Both objects have a boolean flag 'flushUpdates'.
This 'flushUpdate' flag is evaluated in our custom findDirty implementation (overwrite of EmptyInterceptor.findDirty).
If 'flushUpdate' is false the findDirty returns an empty array.
What we are doing:
1. We get the UserBO object from the database (thus in managed state)
2. We create a new InternalOrganizationBO object
3. We set the flushUpdates flag for the UserBO and InternalOrganizationBO to false
4. We set the InternalOrganizationBO object for the User
5. Call the entityManager.flush method
What we think is a bug:
The entityManager.flush method is triggering a SQL INSERT for the InternalOrganizationBO
ALTHOUGH the findDirty method returns for both objects an empty array AND we didn't
call an explcit persist for the InternalOrganizationBO.
(By the way: the 'findDirty' seems to work fine when both objects are already in the database and in a managed stated)
So the questions:
Is this really how the Hibernate framework should behave?
Shouldn't the 'empty array' returned from findDirty prevent an insert?
Are there other options to prevent Hibernate to do the insert?
--- Source Code Entity Classes:
#Entity
#SqlResultSetMapping(....)
#Table(schema="...", name="...")
#IdClass(UserBO_PK.class)
public class UserBO extends AbstractBO implements Serializable {
...
#ManyToOne(fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.DETACH, CascadeType.PERSIST})
#JoinColumn(name=UserBO.INTERNALORGANIZATION_ID)
protected InternalOrganizationBO internalOrganization;
...
#Transient
protected boolean flushUpdates = false;
public boolean isFlushUpdates() {
return flushUpdates;
}
...
}
#Entity
#SqlResultSetMapping(
name="InternalOrganizationBO",
entities = #EntityResult(entityClass=InternalOrganizationBO.class),
columns = { #ColumnResult (name="externalName")})
#Table(schema="...", name="...")
#IdClass(InternalOrganizationBO_PK.class)
public class InternalOrganizationBO extends AbstractBO implements Serializable {
...
#OneToMany(mappedBy="internalOrganization", fetch=FetchType.LAZY, cascade={CascadeType.MERGE, CascadeType.DETACH, CascadeType.PERSIST})
protected List<UserBO> users;
...
#Transient
protected boolean flushUpdates = false;
public boolean isFlushUpdates() {
return flushUpdates;
}
...
}
--- SourceCode TestCase
//load user objects
UserBO user = entityManager.find(UserBO.class, new UserBO_PK("testUser"));
//create internal organization
intOrg01 = new InternalOrganizationBO(intOrg01_id);
intOrg01.setCode(intOrg01_code);
intOrg01.setBusinessUnit(BusinessUnitEnum.empty);
//set all flush updates to false
intOrg01.setFlushUpdates(false);
user01.setFlushUpdates(false);
//add intOrg to user
user01.setInternalOrganization(intOrg01);
--- custom impl. for findDirty:
#Override
public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) {
/**
* logic impl.
*/
if (entity instanceof AbstractBO) {
if (((AbstractBO) entity).isFlushUpdates()) {
return null;
} else {
logger.info("Ignoring flush for object: " + entity.getClass().getSimpleName() + ", "
+ entity.toString());
return new int[0];
}
}
}
//flush entityManager
//THIS triggers an insert of intOrg ! (no update to the User table)
entityManager.flush();
I'm new to using JPA and trying to transition my code from JdbcTemplate to JPA. Originally I updated a subset of my columns by taking in a map of the columns with their values and created the SQL Update string myself and executed it using a DAO. I was wondering what would be the best way to do something similar using JPA?
EDIT:
How would I transform this code from my DAO to something equivalent in JPA?
public void updateFields(String userId, Map<String, String> fields) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append("='");
sb.append(StringEscapeUtils.escapeEcmaScript(entry.getValue()));
sb.append("', ");
}
String str = sb.toString();
if (str.length() > 2) {
str = str.substring(0, str.length() - 2); // remove ", "
String sql = "UPDATE users_table SET " + str + " WHERE user_id=?";
jdbcTemplate.update(sql, new Object[] { userId },
new int[] { Types.VARCHAR });
}
}
You have to read more about JPA for sure :)
Once entity is in Persistence Context it is tracked by JPA provider till the end of persistence context life or until EntityManager#detach() method is called. When transaction finishes (commit) - the state of managed entities in persistence context is synchronized with database and all changes are made.
If your entity is new, you can simply put it in the persistece context by invoking EntityManager#persist() method.
In your case (update of existing entity), you have to get a row from database and somehow change it to entity. It can be done in many ways, but the simpliest is to call EntityManager#find() method which will return managed entity. Returned object will be also put to current persistence context, so if there is an active transaction, you can change whatever property you like (not the primary key) and just finish transaction by invoking commit (or if this is container managed transaction just finish method).
update
After your comment I can see your point. I think you should redesign your app to fit JPA standards and capabilities. Anyway - if you already have a map of pairs <Attribute_name, Attrbute_value>, you can make use of something called Metamodel. Simple usage is shown below. This is naive implementation and works good only with basic attributes, you should take care of relationships etc. (access to more informations about attributes can be done via methods attr.getJavaType() or attr.getPersistentAttributeType())
Metamodel meta = entityManager.getMetamodel();
EntityType<User> user_ = meta.entity(User.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<User> update = cb.createCriteriaUpdate(User.class);
Root e = update.from(User.class);
for( Attribute<? super User, ?> attr : user_.getAttributes() ) {
if (map.containsKey(attr.getName())) {
update.set(attr, map.get(attr));
}
}
update.where(cb.equal(e.get("id"), idOfUser));
entityManager.createQuery(update).executeUpdate();
Please note that Update Criteria Queries are available in JPA since 2.1 version.
Here you can find more informations about metamodel generation.
Alternatively to metamodel you can just use java reflection mechanisms.
JPA handles the update. Retrieve a dataset as entity using the entitymanager, change the value and call persist. This will store the changed data in your db.
In case you are using Hibernate(as JPA provider), here's an example
Entity
#Entity
#Table(name="PERSON")
public class Person {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="NAME", nullable=false)
private String name;
other fields....
}
DAO
public interface PersonDao {
Person findById(int id);
void persist(Person person);
...
}
DaoImpl
#Repository("personDao")
public class PersonDaoImpl extends AnAbstractClassWithSessionFactory implements PersonDao {
public Person findById(int id) {
return (Person) getSession().get(Person.class, id);
}
public void persist(Person person){
getSession().persist(person);
}
}
Service
#Service("personService")
#Transactional
public class PersonServiceImpl implements PersonService {
#Autowired
PersonDao personDao;
#Override
public void createAndPersist(SomeSourceObject object) {
//create Person object and populates with the source object
Person person = new Person();
person.name = object.name;
...
personDao.persist(person);
}
#Override
public Person findById(int id) {
return personDao.findById(id);
}
public void doSomethingWithPerson(Person person) {
person.setName(person.getName()+" HELLO ");
//here since we are in transaction, no need to explicitly call update/merge
//it will be updated in db as soon as the methods completed successfully
//OR
//changes will be undone if transaction failed/rolledback
}
}
JPA documentation are indeed good resource for details.
From design point of view, if you have web interfacing, i tends to say include one more service delegate layer(PersonDelegateService e.g.) which maps the actual data received from UI to person entity (and viceversa, for display, to populate the view object from person entity) and delegate to service for actual person entity processing.
I have run into a nasty bug with jpa and hibernate. I have a billing class with the following annotation:
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="ch1_id", referencedColumnName="id")
private List<BillingItem>billingItems = new ArrayList<BillingItem>();
Now I need to filter deleted items from the collection but cannot use anything but jpa. No hibernate specific annotations allowed. So I wrote a post load function:
#PostLoad
public void postLoad() {
ArrayList<BillingItem>tempItems = new ArrayList<BillingItem>();
Iterator<BillingItem> i = this.billingItems.iterator();
BillingItem item;
while(i.hasNext()) {
item = i.next();
if( item.getStatus().equals("D")) {
tempItems.add(item);
}
}
this.billingItems.removeAll(tempItems);
}
However when there are deleted items to filter I'm seeing
Hibernate: update billing_on_item set ch1_id=null where ch1_id=? and id=?
which produces an exception because ch1_id is a foreign key and cannot be null. However hibernate is binding the parameters to correct values. Why is this update occurring in the first place? How do I correct the error?
Thanks in advance,
Randy
By removing the items from the collection, you're telling Hibernate that the association between the two entities doesn't exist anymore, so obviously, Hibernate removes what materializes this association in the database: it sets the foreign key to null.
What you probably want is just a getter in your entity that returns only the non-deleted items:
public List<BillingItem> getNonDeletedItems() {
List<BillingItem> result = new ArrayList<BillingItem>();
for (BillingItem item : this.billingItems) {
if (!"D".equals(item.getStatus()) {
result.add(item);
}
}
return result;
}
The #OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) line says that it will cascade ALL updates. Look into CascadeType.