How to fix validation constraints in embedded object beeing ignored? - java

I have my entity object in Spring Boot & Hibernate REST API. This class has many fields. Part of them is embedded and validation constraints such as #Min #Max are not working on fields in #Embeddable class. Same validation rules work perfect in #Entity classes. I am using javax.validation.constraints.Max
My main object looks like this:
#Entity
public class Notice extends BaseEntity {
#Embedded
private MyEmbeddedClass myEmbeddedClass;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "entity_class_id")
private MyEntityClass myEntityClass;
}
And my #embedded class:
#Embeddable
public class MyEmbeddedClass {
#Size(max = 50)
private String label;
#Max(100)
private Integer percent;
}
#Max constraint on percent field is ignored, but #size is working perfectly
#Entity
public class MyEntityClass extends BaseEntity {
#Size(max = 50)
private String name;
#Max(6000)
private Integer size;
}
And here #Max constraint and #size constraint on fields size are beeeing created
Is there a way to fix this? My Spring boot version is 2.1.1 and I can create my database scripts manually but I'd like to avoid that and get almost perfect script thanks to hibernate

You need to add #Valid annotation on your embedded object if you want to validate the constraints defined in your #Embeddable object:
#Entity
public class Notice extends BaseEntity {
#Embedded
#Valid
private MyEmbeddedClass myEmbeddedClass;
...
}

Related

Javers with Spring Boot returning ENTITY_INSTANCE_WITH_NULL_ID for a #Transient field

I've just started using Javers on my Application but I have entities annoted with #Transient that I thought Javers would ignore than, but no :(, instead it's throwing me an exception:
JaversException ENTITY_INSTANCE_WITH_NULL_ID: Found Entity instance 'ProductData' with null Id-property 'id'
Do you guy know if there is a way to Ignore those transient fields?
The Documentation says that the #Transient annotation is a synonym for #DiffIgnore. But i dont know if that is related to only comparacion, or during the audit flow as well.
Here is my code:
#Entity
public class ProductExternal extends AbstractEntity implements ExternalEntity {
#Transient
private ProductData productData;
#NotNull
#Column(unique=true)
private Long externalId;
public ProductExternal() { }
//get set
}
--
#Entity
public class ProductData extends AbstractEntity {
private static final long serialVersionUID = 1L;
#Column
#NotNull
private String name;
public ProductData() { }
//get set
}
Parent class
#MappedSuperclass
public abstract class AbstractEntity implements Serializable {
public AbstractEntity() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
#Version
#Column(columnDefinition = "bigint default '0'")
protected Long version;
//get set
}
Your class and mapping (annotations) seems fine. The exception is saying:
Found Entity instance 'ProductData' with null Id-property 'id'
So you are trying to commit to Javers an object of class ProductData which has null id field. Obviously that's not possible. That's a common issue with Hibernate's #GeneratedValue magic. Your field is null at the first place, and then it's being updated later by Hibernate after calling DB sequence next val.
Generally, you should call Javers commit() after Hibernate is done with persisting your object. It can be easily achieved when using one of Javers' auto-audit aspects: #JaversAuditable or #JaversSpringDataAuditable. They are applied in the right phase and call Javers commit() for you. See https://javers.org/documentation/spring-integration/#auto-audit-aspect.

Spring boot JPA - Lazy loading is not working for One to One mapping

Please note that I have looked at similar questions and I have explained why they haven't worked for me
I have a simple Spring boot JPA-Hibernate application with one to one mapping between User and Address. (Please note that I do not have this issue with one to many mapping)
User Entity
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "user")
private Address address;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<Note> notes;
}
Address Entity
#Entity
#Table(name = "addresses")
public class Address implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String street;
#Column
private String city;
#JsonIgnore
#OneToOne
#JoinColumn(name = "user_id")
private User user;
}
Note Entity
#Entity
#Table(name = "notes")
public class Note implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#Column
private String date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
My problem is that whenever I call the controller mapped to get all users I was getting the address and all the associated notes with it as well. But I would expect FetchType.LAZY to take care of that.
I read a lot of questions on StackOverflow mentioning that Jackson might be the culprit here:
Post 1
I also read that spring.jpa.open-in-view defualt value might be the culprit:
Post 2
Post 3
So i tried the following options:
I disabled default open in view property by adding spring.jpa.open-in-view=false to my application.properties which started giving me
Could not write JSON: failed to lazily initialize a collection of role error
I am assuming its because Jackson is calling the getters on my lazily loaded objects so I followed the instructions from another post and added the following for Jackson to leave the lazily loaded collections alone:
pom.xml
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.9</version>
</dependency>
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof org.springframework.http.converter.json.MappingJackson2HttpMessageConverter) {
ObjectMapper mapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
mapper.registerModule(new Hibernate5Module());
}
}
}
}
This solution above fixed the issue with the One to Many mapping but still has the Address associated in the response.
I am not sure what can I do here. The User Entity on the default landing page does not need any address details so I do not want to load it on the landing page. When the record is clicked then it navigates to another page and that's where I would like all the lazy loaded objects to be returned in the response.
I have tried everything I could find online but still nothing has worked so far. I would really appreciate some help with this.
As mentioned by one of the users that it might a duplicate of another question on SO:
Suggested Possible duplicate
I would like to mention that I got the Lazy loading working by disabling spring.jpa.open-in-view property but adding
mapper.registerModule(new Hibernate5Module());
brings back the address associated to the User in the response.
It's working as in the JPA spec:-
Refer the below URL
https://javaee.github.io/javaee-spec/javadocs/javax/persistence/FetchType.html
LAZY fetching strategy is only a hint (as the javadoc says the data can be lazily fetched).. not a mandatory action.
Eager is mandatory (as the javadoc says the data must be eagerly fetched).
You may take a look at Jackson Serialization Views.
I´ve taken a look into the Hibernate5 module you tried and it has some interesting features... but none should fix this issue out of the box for you.
By the way, I normally fix this issue by not returning the Entity as the response but DTOs instead.
The problem is jackson triggering initialization when he writes the JSON, so just don't write the current field (address). But you should not use #jsonIgnore so at other places you could return an Eager obj.
You can use the #jsonView annotation that can provide different JSON for the same obj at different requests. You can look this example :
Create view class:
public class ViewFetchType {
static class lazy{ }
static class Eager extends lazy{ }
}
Annotate your Entity
#Entity
public class User {
#Id
#JsonView(ViewFetchType.Lazy.class)
private String id;
#JsonView(ViewFetchType.Eager.class)
#OneToOne( fetch = FetchType.LAZY)
private Address address ;
}
Specify the FetchType class in your controller:
public class UserController {
private final UserRepository userRepository;
#Autowired
UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
#RequestMapping("get-user-details")
#JsonView(ViewFetchType.Eager.class)
public #ResponseBody Optional<User> get(#PathVariable String email) {
return userRepository.findByEmail(email);
{
#RequestMapping("get-all-users")
#JsonView(ViewFetchType.Lazy.class)
public #ResponseBody List<User> getUsers() {
return userRepository.findAll();
}
}
Here is the answer that i took the idea from... https://stackoverflow.com/a/49207551/10162200

#Column and #Enumerated doesn't work in embeded entity

I have main entity:
#Entity
#Table(name = "partners")
public class Partner {
#ElementCollection
#CollectionTable(
name = "external_login",
joinColumns = #JoinColumn(name = "partner_id")
)
private List<ExternalLogin> externalLogins;
...
}
And ExternalLogin is embeded entity
#Embeddable
public class ExternalLogin {
#Column(name = "type")
#Enumerated(value = EnumType.STRING)
private ExternalLoginType type;
#Column(name = "login")
private String login;
#Column(name = "password_value")
private String passwordValue;
}
public enum ExternalLoginType {
ABC;
}
#Column and #Enumerated not works in ExternalLogin entity.
For example in query will be external_login.passwordValue instead of external_login.password_value.
#Enumerated(value = EnumType.STRING) doesn't work too. Hibernate is trying to get int value of filed instead string.
Can anyone help me?
You misuse annotation #Embeddable. See description in oracle docs https://docs.oracle.com/javaee/6/api/javax/persistence/Embeddable.html
Defines a class whose instances are stored as an intrinsic part of an owning entity and share the identity of the entity. Each of the persistent properties or fields of the embedded object is mapped to the database table for the entit
#Embeddable annotation makes sense only for singular assotiation fields. Annotating list fields as #Embeddable is wrong.
Just replace
#Embeddable
public class ExternalLogin {
to
#Entity
public class ExternalLogin {
I had exactly the same issue just now.
The solution for me ended up being adding
#Access(FIELD)
To the Embeddable object.

#OneToOne annotation within composite key class is not working

Maybe somebody can clarify what is wrong with the code below. When I create one-to-one association within embedded class (it is composite primary key) like in the code below:
#Entity
public class Test {
#EmbeddedId
private TestId id;
#Embeddable
public static class TestId implements Serializable {
private static final long serialVersionUID = 1950072763330622759L;
#OneToOne(optional = false)
#JoinColumn(name = "linkedTable_id")
private LinkedTable linkedTable;
}
..........
}
I get the following stack trace:
--------------------------------------------
Caused by: java.lang.NullPointerException
at org.hibernate.cfg.AnnotationBinder.bindOneToOne(AnnotationBinder.java:1867)
at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1286)
at org.hibernate.cfg.AnnotationBinder.fillComponent(AnnotationBinder.java:1662)
at org.hibernate.cfg.AnnotationBinder.bindId(AnnotationBinder.java:1695)
at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1171)
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:706)
at org.hibernate.cfg.AnnotationConfiguration.processArtifactsOfType(AnnotationConfiguration.java:452)
at org.hibernate.cfg.AnnotationConfiguration.secondPassCompile(AnnotationConfiguration.java:268)
at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1121)
at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1211)
at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:154)
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:847)
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:178)
at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:235)
... 26 more
What is interesting why the sample above works if I change association type to many-to-one and doesn't work with one-to-one?
I wasn't aware this was possible but, according to the Hibernate Annotation reference documentation, it is (this is Hibernate specific though):
2.2.3.2.1. #EmbeddedId property
(...)
While not supported in JPA, Hibernate
lets you place your association
directly in the embedded id component
(instead of having to use the
#MapsId annotation).
#Entity
class Customer {
#EmbeddedId CustomerId id;
boolean preferredCustomer;
}
#Embeddable
class CustomerId implements Serializable {
#OneToOne
#JoinColumns({
#JoinColumn(name="userfirstname_fk", referencedColumnName="firstName"),
#JoinColumn(name="userlastname_fk", referencedColumnName="lastName")
})
User user;
String customerNumber;
}
#Entity
class User {
#EmbeddedId UserId id;
Integer age;
}
#Embeddable
class UserId implements Serializable {
String firstName;
String lastName;
}
And with the code you provided, the following snippet just works for me:
LinkedTable linkedTable = new LinkedTable();
linkedTable.setId(1l);
session.persist(linkedTable);
session.flush();
Test.TestId testId = new Test.TestId();
testId.setLinkedTable(linkedTable);
Test test = new Test();
test.setId(testId);
session.persist(test);
session.flush();
Tested with Hibernate EM 3.4.0.GA, Hibernate Annotations 3.4.0.GA and Hibernate Core 3.3.0.SP1.
If it doesn't work for you, can you provide a bit more code allowing to reproduce the problem?

GAE unowned JPA relationship

I have an unowned relationship in my Domain model
#Entity
public class A {
#Id
private String id;
private Key firstB;
private Key secondB;
// getters & setters
}
#Entity
public class B {
#Id
private Key id;
private String name;
// getter & setter
}
KeyFactory.createKey(B.class.getSimpleName(), name) is the way I generate the Key for class B
I save B independently from A and assign it to an instance of A some time. The problem is that after saving A both fields firstB and firstA are null.
Any idea of what I'm doing wrong?
Key objects are not persisted by default so require explicit annotation which is why you are seeing null values.
Try annotating firstB and secondB as #Enumerated (this should really be #Basic but there is a bug which prevents this from working):
#Entity
public class A {
#Id
private String id;
#Enumerated
private Key firstB;
#Enumerated
private Key secondB;
}
Update: The latest SDK and DataNucleus JARs now correctly allow the use of #Basic.

Categories

Resources