#Value with final field - spring

#Value("${a.b.cache.expireInMinutes:15}")
private final long expireInMinutes;
Looks like sometimes the final field with annotation #Value cannot pick up the value from application.yaml:
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'long' available
Update
If we do method injection, such issue disappears!

The only way you can inject values into a final field is through Constructor Injection, If it is sometimes working in your case it is because of Spring doing some nasty stuff. I suggest removing the final from the variable.

Related

Hibernate Upgrade to v6 (Spring Boot 3) - "Named type [interface java.util.List] did not implement BasicType nor UserType"

After upgrading to Spring Boot 3 / Hibernate 6, I am getting exceptions during Spring application / test startup.
java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration#15ec3c0c testClass = ....
Caused by: jakarta.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.IllegalArgumentException: Named type [interface java.util.List] did not implement BasicType nor UserType
Problem can be traced / debugged down to a List<String> entity property, which is using an AttributeConverter for storing it as a comma-separated string in the (MySQL) DB (TEXT field).
The converter:
#Converter
class StringListToStringConverter : AttributeConverter<List<String>, String> {
....
}
The entity:
#MappedSuperclass
abstract class MyInstance<T>(
...
#Column(name = "iface_ids", columnDefinition = "TEXT")
#Convert(converter = StringListToStringConverter::class)
var interfaceIds: List<String> = emptyList()
...
)
This has been working nicely before with Spring Boot 2.7.x / Hibernate 5.x.
Do you think there is another solution than writing a custom type (and thus removing/replacing the AttributeConverter) in this case?
Kind Regards,
dom
Expecting it to actually work OOTB, the AttributeConverter solution looks so clean&simple for this purpose compared to a custom type implementation that I don't really want to change that.

Why to use #AllArgsConstructor and #NoArgsConstructor together over an Entity?

I have seen multiple codes online on applications of Spring Boot in IntelliJ, and many use both #AllArgsConstructor and #NoArgsConstructor together and both are constructors however the purpose of each is different -
#AllArgsConstructor generates a constructor requiring argument for every field in the annotated class
#NoArgsConstructor generates a constructor with no parameter
Then why are we using both together over the same entity and how do they function in such case?
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class Product {
#Id
private int id;
private String name;
private String type;
}
The JPA specification requires that all persistent classes (#Entity) have a no-arg constructor, public or protected. (note that this is not necessarily true when dealing with some implementation like Hibernate, see this answer).
This is needed because JPA uses the default constructor method to create a bean class using the reflection API. Indeed if your class would contain many constructors then JPA wouldn't know which one to call, this is why it instantiates the class through its no-arg constructor using reflections :
Product.class.newInstance();
which is equivalent to new Product() (Product.class is a class literal, it can fail at runtime if the class is not found in the classpath), then, once instantiated, uses fields setters to deal with it.
Then, in Java, a default constructor (no-argument constructor) is automatically generated for a class unless you define other constructors (it only does when you don't provide any other constructor).
So because the compiler automatically creates a default no-arg constructor when no other constructor is defined, only classes that define constructors must also include a no-arg constructor if required by a framework (here JPA). This is why you need to add #NoArgsConstructor annotation if you add the #AllArgsConstructor annotation.
Also note that you are using #Data, which bundles the features of #RequiredArgsConstructor, which will generate a constructor for all final or #NonNull annotated fields (see Lombok documentation). So as you are using only non final nullable fields, it may generate an empty constructor even if you don't add the #NoArgsConstructor annotation. I have not tested that last case though, I know that it generates an empty constructor when using #RequiredArgsConstructor directly with non final nullable fields, but I don't know if it works the same when using #Data.
#Data also bundles #ToString, so you don't need to add it again.
I'm personnaly not a fan of using #Data if I don't need all the bundled annotations, so I usually simply use :
#Entity
#Getter
#Setter
#EqualsAndHashCode
public class Product {
#Id
private int id;
private String name;
private String type;
}
as I often don't use toString() nor parameterized constructor. It may be more verbose but more meaningful to me.
These are the annotations from Lombok. To understand why it's is needed you have to understand how things work internally.
JPA says
It's specification says "The JPA specification requires that all persistent classes have a no-arg constructor. This constructor may be public or protected. Because the compiler automatically creates a default no-arg constructor when no other constructor is defined, only classes that define constructors must also include a no-arg constructor."
To understand further, when it creates and entity using reflection it uses Class.newInstance() method which requires a no-argument constructor to create an instance.
The most common type of dependency injection used by Spring is
Constructor based Injection
Setter based Injection
Constructor based Injection (#AllArgsConstructor): When you create object by passing all parameters, you basically use a constructor injection. It should be done when we have all parameter values and we want to create an object with all values initialized.#AllArgsConstructor generates a constructor requiring an argument for every field in the annotated class.
Setter based Injection (#NoArgsConstructor): We create an object first (uses no arg-constructor) and then update the dependencies or values later by using setters.#NoArgsConstructor generates a default constructor with no parameters.
There are many key differences between constructor injection and setter injection.
Partial dependency: can be injected using setter injection but it is not possible by constructor. Suppose there are 3 properties in a
class, having 3 arg constructor and setters methods. In such case, if
you want to pass information for only one property, it is possible by
setter method only.
Overriding: Setter injection overrides the constructor injection. If we use both constructor and setter injection, IOC container will
use the setter injection.
Changes: We can easily change the value by setter injection. It doesn't create a new bean instance always like constructor. So setter
injection is flexible than constructor injection.
#NoArgsConstructor will generate a constructor with no parameters. If this is not possible (because of final fields), a compiler error will result instead, unless #NoArgsConstructor(force = true) is used, then all final fields are initialized with 0 / false / null. For fields with constraints, such as #NonNull fields, no check is generated,so be aware that these constraints will generally not be fulfilled until those fields are properly initialized later. Certain java constructs, such as hibernate and the Service Provider Interface require a no-args constructor. This annotation is useful primarily in combination with either #Data or one of the other constructor generating annotations.
#AllArgsConstructor generates a constructor with 1 parameter for each field in your class. Fields marked with #NonNull result in null checks on those parameters.
Conclusion:
#AllArgsConstructor generates a constructor requiring an argument for every field in the annotated class.
#AllArgsContstructor also allows the creation of static factory
methods using the staticName attribute
#NoArgsConstructor generates a default constructor with no parameters
#NoArgsConstructor can create a static factory method for construction purposes
Lombok can't call the super constructor unless it has a no-args constructor
If the superclass doesn't have a no-args constructor, Lombok can't generate any constructor in the subclass

quarkus and hibernate mapping (field access)

I moved an entity which worked in JEE wildfly to quarkus:
#Entity
#Getter
#Setter
public class Item {
#Column(name = "archived")
private OffsetDateTime archived;
public boolean isArchived() {
return archived != null;
}
}
After running in dev-mode, I get this error:
Caused by: org.hibernate.MappingException:
In trying to locate getter for property [archived],
Class [com.Item]
defined both a `get` [public java.time.OffsetDateTime com.Item.getArchived()]
and `is` [public boolean com.Item.isArchived()] variant
at org.hibernate.internal.util.ReflectHelper.checkGetAndIsVariants(ReflectHelper.java:538)
at org.hibernate.internal.util.ReflectHelper.verifyNoGetVariantExists(ReflectHelper.java:562)
at org.hibernate.internal.util.ReflectHelper.getGetterOrNull(ReflectHelper.java:502)
at org.hibernate.internal.util.ReflectHelper.findGetterMethod(ReflectHelper.java:424)
at org.hibernate.internal.util.ReflectHelper.getterMethodOrNull(ReflectHelper.java:571)
at org.hibernate.property.access.internal.PropertyAccessMixedImpl.getAccessType(PropertyAccessMixedImpl.java:97)
at org.hibernate.property.access.internal.PropertyAccessMixedImpl.<init>(PropertyAccessMixedImpl.java:47)
at org.hibernate.property.access.internal.PropertyAccessEnhancedImpl.<init>(PropertyAccessEnhancedImpl.java:28)
at org.hibernate.property.access.internal.PropertyAccessStrategyEnhancedImpl.buildPropertyAccess(PropertyAccessStrategyEnhancedImpl.java:27)
at org.hibernate.mapping.Property.getGetter(Property.java:311)
at org.hibernate.tuple.entity.PojoEntityTuplizer.buildPropertyGetter(PojoEntityTuplizer.java:255)
at org.hibernate.tuple.entity.AbstractEntityTuplizer.<init>(AbstractEntityTuplizer.java:142)
at org.hibernate.tuple.entity.PojoEntityTuplizer.<init>(PojoEntityTuplizer.java:59)
... 35 more
I understand the error but I dont know why does this hibernate mapping work in wildfly and not in quarkus? As I declared jpa field access instead of jpa property access, hibernate should see the isArchived() automatically as transient.
Or does quarkus compiles all these field-injections to property-injections?
Quarkus generates a getter for the archived field.
Here Hibernate complains that you have two accessors for the archived field: isArchived() and getArchived().
This is an Hibernate issue/limitation, nothing specific to Quarkus.
I think the best is to rename your isArchived() method as Hibernate don't know which method to use to retrieve the value of the archivedfield. And if you add #Transient to your isArchived() method it could (depending on wich method it get first) handle your field as transient.

Load Spring configuration in Hibernate entity

I want to configure Hibernate to set maximum length for a VARCHAR field. This maximum length is defined in a configuration file, and this configuration file is loaded by a class ValidationConfiguration.
This is my entity:
#Entity
public class MyEntity{
#Autowired /*I know this is bad practice,
I just want to let readers know that this object is instantiated.*/
private ValidationConfiguration config;
#Column(length = config.getMaxLength()) /*This gives a "java: element value
must be a constant expression"*/
String description;
//Get and set
}
Is this possible? If not, are there any workarounds?
From your code, it is clearly visible, that You are just defining the ValidationConfiguration by private ValidationConfiguration config;
But You are not Instantiating the Object.
So, Instantiate the object like new ValidationConfiguration() and since you haven't shared the code of ValidationConfiguration , i am predicting that your method is getMaxLength() not static. If the problem persists, do share the ValidationConfiguration code.
If that is a bean, then you can autowire it simply and don't create new instantiation.

Bind ZonedDateTime to bean from application.properties

I want to bind the current date to a bean via application.properties. I tried using a spell expression but it didn't work.
application.yml
defaults:
date: #{T(java.time.ZonedDateTime).now()}
And I have a java bean with getters and setters.
#ConfigurationProperties(prefix = "defaults")
public class DefaultProperties {
private ZonedDateTime date;
// Getters & Setters
}
The trouble is that the expression is always parsing as a string . I am getting Reason: Failed to convert property value of type 'java.lang.String' to required type 'java.time.ZonedDateTime'
Is there any way of doing this?

Resources