Strange Lombok behaviour with Quarkus and Jackson - java

at the moment i'm facing a strange issue. I use lombok in my Quarkus project to have getter, setter etc. generated automatically. When i compile Quarkus to a native image, Jackson refuses to serialize a Lombok-Data-Object, but serializes a different one without problems.
Even stranger is, that this error only occurs when i compile a native binary and embed it into a container. Running both examples in the "quarkus:dev" profile works flawless.
Objects from this class get serialized:
#Data
#Entity
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "accounts")
public class AccountEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#Column(unique = true, name = "username", nullable = false)
private String username;
#Column(unique = true, name = "mail", nullable = false)
private String mail;
#Column(name = "password", nullable = false)
private String password;
}
Objects from this class get not:
#Getter
#AllArgsConstructor
public class LoginResponse {
private final String token;
}
The error message:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class de.alexzimmer.pwa.model.LoginResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
But even if i take a look into the generated class-files, i can see public getters for both classes getting generated. I'm thankful for any advices and thoughts of how this could happen.
Thanks!

You have to register this class for reflection by adding the #RegisterForReflection annotation.
It works for the first object as it's an entity and this is done automatically.
See https://quarkus.io/guides/writing-native-applications-tips#registering-for-reflection for a full explanation.
I will probably add the Jackson error message there so that it can be found more easily.

Related

I have a problem when I use Value object with JPA

I must use ValueObject in the project and JPA at the same time, but when changing the attribute to valueobject it gives me an error, I don't know yet how to solve the problem
this error: ('Id' attribute type should not be 'BrandCodigo') ('Basic' attribute type should not be 'BrandNombre' )
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Entity(name = "Brand")
#Table(name = "marcas",
uniqueConstraints = {
#UniqueConstraint(name = "uk_marcas_nombre",columnNames = "nombre")
})
public class Brand {
#Id
#Column(name = "codigo")
private BrandCodigo codigo;
#Column(name = "nombre",
nullable = false,
columnDefinition = "varchar(80)"
)
private BrandNombre nombre;
}
When you refer to lombok's #Value then this is not possible afaik. Lombok's #Value is for immutable objects.
But your entity needs to be mutable since the way JPA constructs it.
Furthermore value objects do not have an identity but database entities should have primary keys.

How to ignore hibernate List<>[] arrays methods from being scanned during startup?

I have a spring-boot application with JPA, and I have the following mapping class:
#Entity
public class Entry {
#Id
#GeneratedValue(generator = "ENTRY_SEQ")
#SequenceGenerator(name = "ENTRY_SEQ", sequenceName = "ENTRY_SEQ")
private Long id;
#ManyToOne
#JoinColumn(name = "CONSTR_TABLE_ID", nullable = false)
private ConstraintTable constraintTable;
#OneToMany(mappedBy = "entry", cascade = CascadeType.ALL, orphanRemoval = true)
private List<EntryValue> entryValues = new ArrayList<>();
// default getters and setters
public List<EntryValue>[] createValuesGroupedByColumn() {
return null;
}
}
Every time I start my application it throws the following exception:
Caused by: java.lang.IllegalArgumentException: No PropertyTypeExtractor available for type void
It's caused because List<EntryValue>[] it's not a valid type supported in JavaReflectionManager.toXType, it only supports arrays, collections and simple types. However I would like to have this method and configure hibernate to ignore it.
In this post was suggested to use the #Transient annotation, but it didn't work. I tried different names that don't start with [get|set|is]AttrName (the standard bean convention) and also didn't work. Is there someway I can ignore this method from being scanned by hibernate during initialization?

HibernateProxy.toString lazy initializacion exception

I'm getting a weird error while I'm debugging my POC.
I have 2 entities:
#Getter
#Setter
#Entity
#Table(name = "APPLICANT")
public class Applicant implements Serializable {
private static final long serialVersionUID = 6060170457948717553L;
#Id
#Column(name = "applicant_id", insertable = false, nullable = false)
private Long applicantId;
#Column(name = "application_id", unique = true)
private String applicationId;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", referencedColumnName = "application_id", insertable =
false, updatable = false)
private ApplicationEntity applicationEntity;
#Getter
#Setter
#Entity
#Table(name = "APPLICATION")
public class ApplicationEntity implements Serializable{
private static final long serialVersionUID = 7300036359295729197L;
#Id
#Column(name = "APPLICATION_ID")
private String id;
#OneToOne(mappedBy = "applicationEntity", fetch = FetchType.LAZY)
private Applicant applicant;
These classes has the repositories interfaces extending from CrudRepository, and in the Applicant repository I have a custom method to get the entity with the applicationId:
Applicant findByApplicationId(String applicationId);
But, when I'm debugging, I see the following message in the intellij debuguer on the applicationEntity attribute:
Method threw 'org.hibernate.LazyInitializationException' exception. Cannot evaluate org.example.postgres.jpa.model.ApplicationEntity$HibernateProxy$qa4PKx8V.toString()
The value qa4PKx8V changes every time that I perform a new test.
I tried a lot of combinations in the #Join annotation, I've deleted the lombook annotations, I've used the #Transactional annotation either, but is always the same error.
A key point to note, is that I can get the data from the table with any error, I just see this message in the debugger, so my question is, this is a thing of intellij or something like that? Or I need to fix this with configuration or changing something in my code?
Thanks.
I am assuming you have an autogenerated toString() implementation?
In general, you should avoid referencing lazily-loaded properties in toString(), equals(), hashCode() etc. Failing to do so will result in LazyInitializationException surprises like the one you're facing, triggered by the aforementioned methods whenever they try to access lazy properties outside of an active transaction context.
(This is indeed 'a thing of intellij', in the sense that although the debugged code is probably surrounded by a transaction, the Intellij inspector evaluates the expression on a separate thread where no transaction is active = no persistence context is open. Also, it will only happen with #XxxToOne(optional = false) properties)

Spring Boot JPA - OneToMany relationship causes infinite loop

I have a two objects with simple #OneToMany relationship which looks as follows:
parent:
#Entity
public class ParentAccount {
#Id
#GeneratedValue
private long id;
private String name;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parentAccount")
private Set<LinkedAccount> linkedAccounts;
}
child:
#Entity
public class LinkedAccount {
#Id
#GeneratedValue
private long id;
#ManyToOne(optional = false)
private ParentAccount parentAccount;
private String name;
// empty constructor for JPA
public LinkedAccount() {
}
}
I ma using Spring CrudRepository to operate with these entities. However, when calling ParentAccount parent = parentAccountRepository.findOne(id);, some kind of infinite loop starts happening and hibernate spams this all over the console:
Hibernate: select linkedacco0_.parent_account_id as parent_a6_1_0_, linkedacco0_.id as id1_0_0_, linkedacco0_.id as id1_0_1_, linkedacco0_.aws_id as aws_id2_0_1_, linkedacco0_.key_id as key_id3_0_1_, linkedacco0_.name as name4_0_1_, linkedacco0_.parent_account_id as parent_a6_0_1_, linkedacco0_.secret_key as secret_k5_0_1_ from linked_account linkedacco0_ where linkedacco0_.parent_account_id=?
I tried changed the fetch type to LAZY but then I get this error:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.berrycloud.scheduler.model.ParentAccount.linkedAccounts, could not initialize proxy - no Session
(It seems that it is trying to do the lazy load outside of the transactional context).
This is my CRUD repository:
#Repository
public interface ParentAccountRepository extends CrudRepository<ParentAccount, Long> {
}
Could someone tell me how to resolve this issue? I would prefer the solution with EAGER fetch. Thank you for any tips
EDIT: here is the schema I am using
CREATE TABLE parent_account (
id BIGINT auto_increment,
name VARCHAR(80) null,
PRIMARY KEY (`id`)
);
CREATE TABLE linked_account (
id BIGINT auto_increment,
parent_account_id BIGINT,
name VARCHAR(80) null,
FOREIGN KEY (`parent_account_id`) REFERENCES `parent_account` (`id`),
PRIMARY KEY (`id`)
);
As the first answer suggests:
Do not use Lombok's #Data annotation on #Entity classes.
Reason: #Data generates hashcode(), equals() and toString() methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString() and it crashes.
Problem solved. I was using a custom #toString method in the LinkedAccount which was referencing the ParentAccount. I had no idea that this could cause any problem and therefor I did not include the toString in my question.
Apparently, this was causing an infinite loop of lazy loading and removing this reference fixed the problem.
As user1819111 told, #Data from Lombok is not compatible with #Entity and FetchType=LAZY. I had used Lombok.Data (#Data) and I was getting this error.
As I don't want do create all get/set, I just put the Lombok #Setter and #Getter in your class and all will work fine.
#Setter
#Getter
#Entity
#Table(name = "file")
#SequenceGenerator(name = "File_Sequence", allocationSize=1, sequenceName = "file_id_seq")
public class MyClass{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "File_Sequence")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "file", cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
private Set<Base2FileDetail> details = new HashSet<>();
}
Something like this does not work?
#Entity
public class Account {
#Id
#GeneratedValue
private long id;
private String name;
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="manager_id")
private Account manager;
#OneToMany((fetch = FetchType.EAGER, mappedBy="manager")
private Set<Account> linkedAccounts = new HashSet<Account>();
}
I recently had this issue due to a poorly defined Jackson2HttpMessageConverter.
I had done something like the following.
#Bean
RestTemplate restTemplate(#Qualifier("halJacksonHttpMessageConverter")
TypeConstrainedMappingJackson2HttpMessageConverter halConverter) {
final RestTemplate template = new RestTemplateBuilder().build();
halConverter.setSupportedMediaTypes(List.of(/* some media types */));
final List<HttpMessageConverter<?>> converters = template.getMessageConverters();
converters.add(halConverter);
template.setMessageConverters(converters);
return template;
}
This caused a problem because the media types did not include all the defaults. Changing it to the following fixed the issue for me.
halConverter.setSupportedMediaTypes(
new ImmutableList.Builder<MediaType>()
.addAll(halConverter.getSupportedMediaTypes())
.add(/* my custom media type */)
.build()
);
This simple way worked for me. Just use JsonIgnoreProperties .
#JsonIgnoreProperties(value = {"linkedAccounts"})
#ManyToOne(cascade = { CascadeType.PERSIST})
#JoinColumn(name = "abc", referencedColumnName = "abc")
private ParentAccount parentAccount;
This way worked for me without removing #ToSring annotation:
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "parent_accounts")
public class ParentAccount {
#JsonIgnoreProperties({"parentAccount"})
#OneToMany(mappedBy = "parentAccount",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<LinkedAccount> linkedAcounts;
// ...
}
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#AllArgsConstructor
#Table(name = "linked_accounts")
public class LinkedAccount {
#JsonIgnoreProperties("linkedAcounts")
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parentAccount_id")
private ParentAccount parentAccount;
// ...
}
PS: In #JsonIgnoreProperties You can also ignore more than one field for preventing infinite loop

Hibernate and JSON - is there a definitive solution to circular dependencies?

I'm struggling with Hibernate entities and JSON in these days and, although there is a lot of questions regarding the object, I'm yet not capable to serialize in presence of circular dependencies. I tried with both Gson and jackson but I didn't get a lot of progresses.
Here is an excerpt from my objects.
This is the "parent" class.
#Entity
public class User extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
#Cascade({CascadeType.SAVE_UPDATE})
private Set<Thread> threads = new HashSet<Thread>(0);
//...other attributes, getters and setters
}
and this is the "children" class
#Entity
#Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author", nullable = true)
private User user;
//...other attributes, getters and setters
}
I've written a simple class to test both gson and jackson features; as said, they both raise an exception.
public class MyJsonsTest
{
private static User u;
public static void main(String[] args)
{
u = new User("mail", "password", "nickname", new Date());
u.setId(1); // Added with EDIT 1
// testGson();
testJackson();
}
private static void testJackson()
{
Thread t = new Thread("Test", u, new Date(), new Date());
t.setId(1); // Added with EDIT 1
u.getThreads().add(t);
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try
{
mapper.writeValue(new File("result.json"), u);
}
catch {/[various exceptions catched, but a JsonMappingException was thrown]}
}
private static void testGson()
{
Gson gson = new Gson();
System.out.println(u.toString());
System.out.println(gson.toJson(u, User.class));
Thread t = new Thread("Test", u, new Date(), new Date());
u.getThreads().add(t);
//This raise an exception overflow
System.out.println(gson.toJson(u, User.class));
}
}
To solve the problem, on jackson side, I tried to use this annotation
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
on both User and Thread class. However, it doesn't solve the problem.
On gson side, I read about the GraphAdapterBuilder class, but I wasn't able to properly use it. I don't find any jar, so I copy/pasted the source code from here. However, there is a compile time error at this line
private final ConstructorConstructor constructorConstructor = new ConstructorConstructor();
because the ConstructorConstructor() is undefined; the right syntax should be
ConstructorConstructor(Map<Type>, InstanceCreator<?> instanceCreators)
So, is there a definitive solution to this problem? Obviously, I can't use transient variables.
EDIT 1
I finally found the issue with jackson. In the test class, I forgot to initialize the id field (in real scenarios it is initialized by the database) and this is the reason of the exception. When I finally set the id, all works. This is the output
{
"id" : 1,
"email" : "mail",
"password" : "password",
"nick" : "nickname",
"registeredDate" : 1414703168409,
"threads" : [ {
"id" : 1,
"thread" : null,
"user" : 1,
"title" : "Test",
"lastModifiedDate" : 1414703168410,
"createdDate" : 1414703168410,
"messages" : [ ],
"threads" : [ ]
} ],
"messages" : [ ]
}
When dealing with circular dependencies you need to build a parent-children JSON hierarchy, because the marshalling must be cascaded from root to the inner-most child.
For bi-directional associations, when the Parent has a one-to-many children collection and the child has a many-to-one reference to Parent, you need to annotate the many-to-one side with #JsonIgnore:
#Entity
#Table(name = "thread")
public class Thread extends RecognizedServerEntities implements java.io.Serializable
{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#org.codehaus.jackson.annotate.JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author", nullable = true)
private User user;
//...other attributes, getters and setters
}
This way you will no longer have a Json serializing-time circular dependency.
Jackson
As said, I was able to solve the problem using
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id", scope=MyEntity.class)`
for each entity as suggested here.
The scope attribute was necessary to make sure that the name "id" is unique within the scope. Actually, without the scope attribute, as you can see here, it throws an exception saying
com.fasterxml.jackson.databind.JsonMappingException: Already had POJO for id java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f] (through reference chain: ParentEntity["children"]->java.util.ArrayList[0]->ChildEntity["id"])
...stacktrace...
Caused by: java.lang.IllegalStateException: Already had POJO for id (java.lang.String) [com.fasterxml.jackson.annotation.ObjectIdGenerator$IdKey#3372bb3f]
...stacktrace...
Gson
I still haven't found a clean way to serialize circular dependencies.
I have done this using org.codehaus.jackson.annotate.JsonManagedReference and org.codehaus.jackson.annotate.JsonBackReference in this way...
look at how i used #JsonManagedReference
#Id
#TableGenerator(name="CatId", table="catTable",pkColumnName="catKey",pkColumnValue="catValue", allocationSize=1)
#GeneratedValue(strategy=GenerationType.TABLE, generator="CatId")
#Column(name = "CategId", unique = true, nullable = false)
private long categoryId;
private String productCategory;
#JsonManagedReference("product-category")
#OneToMany(targetEntity=ProductDatabase.class,mappedBy="category", cascade=CascadeType.ALL, fetch=FetchType.EAGER)
private List<ProductDatabase> catProducts;
and then at the other end i used #JsonBackReference as shown below.
#Id#GeneratedValue
private int productId;
private String description;
private int productPrice;
private String productName;
private String ProductImageURL;
#JsonBackReference("product-category")
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CategId")
private Category category;
just apply these annotations and check if it works for you.
Its not good design to serialize Hibernate POJO to client. As you may send some data to client location, which he is not authorize to view. You should create client POJO and copy data from hibernate POJO to client POJO, which you want to send to client. If you don't want to do that, you can use #JsonIgnore or Fetch all data eagerly.

Categories

Resources