I am using the JOOQ library in order to fetch the result from a select query into my custom DTO class. Because my Custom DTO class has an ENUM Type field mapped as an Integer Column in my database I am using a custom data type converter. The query I perform is just a basic select query:
public TestSuiteDto findByAuditTestSuiteId(Integer auditTestSuiteId) {
TestSuiteJTable ts = TestSuiteJTable.TEST_SUITE;
AuditTestSuiteJTable ats = AuditTestSuiteJTable.AUDIT_TEST_SUITE;
List<TestSuiteDto> result = dsl.select(
ts.ID,
ts.DESCRIPTION,
ts.PROFILE_TYPE_ID,
ts.CREATED,
ts.ACTIVE,
ts.DEPRECATION,
ts.FEEDBACK_QUESTIONNAIRE_ID)
.from(ts
.join(ats).on(ts.ID.eq(ats.TEST_SUITE_ID)))
.where(ats.ID.eq(auditTestSuiteId))
.fetchInto(TestSuiteDto.class);
//do some stuff before returning
return result.get(0);
}
My custom DTO class looks like this
#Data
public class TestSuiteDto {
private Integer id;
private String description;
private ProfileType profileType;
private LocalDateTime created;
private boolean active;
private LocalDateTime deprecation;
private Integer feedbackQuestionnaireId;
}
The problem is that during the fetching process the SETTER of the DTO class is never triggered for the ENUM type field e.g. profileType even though I have configured a custom data type converter:
#Slf4j
public class ProfileTypeConverter implements Converter<Integer, ProfileType> {
#Override
public ProfileType from(Integer databaseObject) {
log.info("ProfileTypeConverter.from {} -> {}", databaseObject, ProfileType.getFromId(databaseObject));
return ProfileType.getFromId(databaseObject);
}
#Override
public Integer to(ProfileType userObject) {
log.info("ProfileTypeConverter.to");
return userObject.getId();
}
#Override
public Class<Integer> fromType() {
log.info("ProfileTypeConverter.fromType");
return Integer.class;
}
#Override
public Class<ProfileType> toType() {
log.info("ProfileTypeConverter.toType");
return ProfileType.class;
}
}
I have added some logs just to check if the converter is triggered at all and I see that the converter is triggered as expected (from method of the Converter class is called during the execution of the JOOQ SQL query). I have also delombok my DTO class in order to add logs in SETTERs and GETTERs and see if those are also properly called. I found out that all the SETTERs are properly called except for the profileType one. Because of that when I retrieve the DTO from the result list the value of the profileType field is null. The column in my database (mysql) that maps to the profileType ENUM is called PROFILE_TYPE_ID and it is of type Integer. I have also configured a forcedType in the pom.xml following the examples on JOOQ documentation webpage.
<forcedType>
<includeExpression>${jdbc.database}.TEST_SUITE.PROFILE_TYPE_ID</includeExpression>
<userType>mypackage.type.ProfileType</userType>
<converter>mypackage.converter.ProfileTypeConverter</converter>
</forcedType>
and this is how I have configured the ProfileType Field in pom.xml
<field>
<expression>${jdbc.database}.TEST_SUITE.PROFILE_TYPE_ID</expression>
<fieldIdentifier>
<expression>PROFILE_TYPE_ID</expression>
</fieldIdentifier>
<fieldMember>
<expression>profileType</expression>
</fieldMember>
<fieldGetter>
<expression>getProfileType</expression>
</fieldGetter>
<fieldSetter>
<expression>setProfileType</expression>
</fieldSetter>
</field>
JOOQ version: 3.14.16, Java 8
Why do things behave this way?
The reason is that you have a name mismatch:
Query
ts.PROFILE_TYPE_ID,
DTO
private ProfileType profileType;
If you want to rely on the reflection based DefaultRecordMapper, then you must name those things accordingly, otherwise, they won't be mapped. The fact that you have a converter is irrelevant, if the names don't match. Imagine you had 20 columns of type ProfileType. You wouldn't want to have DefaultRecordMapper map values purely based on their type.
Regarding your comments:
I have added some logs just to check if the converter is triggered at all and I see that the converter is triggered as expected (from method of the Converter class is called during the execution of the JOOQ SQL query)
Yes of course. The Converter belongs to the projected column. The conversion happens before the mapping (i.e. the into(TestSuiteDto.class) call)
Solutions
There are multiple alternatives to solve this:
Call your DTO attribute profileTypeId, or to add JPA annotations to it to map between SQL names and Java names
Rename your SQL column (in DDL)
Alias your SQL column using PROFILE_TYPE_ID.as("profile_type")
Use a computed column PROFILE_TYPE and attach the converter to that, keeping the PROFILE_TYPE_ID as it is (you can also use client side computed columns for that, in order not to affect your schema)
Use type safe constructor based mapping, rather than reflection based mapping, e.g. using fetch(Records.mapping(TestSuiteDto::new))
There are probably more possible solutions.
I have similar problem like this [Hibernate Exception: Unknown name value for enum class
But in my case,
Unable to filter, so returning non filtered results.Unknown name value for enum class com.xxxx.enums.Status: DELIVERED
java.lang.IllegalArgumentException: Unknown name value for enum class com.xxxx.enums.Status: DELIVERED
at org.hibernate.type.EnumType.nullSafeGet(EnumType.java:128)
at org.hibernate.type.CustomType.nullSafeGet(CustomType.java:109)
at org.hibernate.type.AbstractType.hydrate(AbstractType.java:104)
#Enumerated(value = EnumType.STRING)
#Column(name = "status", length = 10)
#AuditableField
private Status status;
public enum ReleaseStatus {
DL("Delivered"),
}
Everything seems fine, still I am getting that exception.
You have the String DELIVERED in your table. And this string is supposed to be the name() of one of the ReleaseStatus instances. And ReleaseStatus doesn't have any instance named DELIVERED. The only one you posted is named DL.
So what should be in the table is DL not DELIVERED. Or you should rename your enum instance to DELIVERED, to match what is stored in the database table.
You could define a custom Hibernate user type and use it for this enum as well, so that when getting "DELIVERED" from the database, Hibernate finds the enum instance constructed with this value (and ignoring the case). But storing the correct value from the start looks like a betteridea to me.
I prefer defining a custom converter like:
#Column
#Convert(converter = StatusFirmaDocumentoConverter.class) <<<<< :)
#AuditableField
private Status status;
(note: do not include the #Enumerated attribute) and creating a converter to process the enumerator value like:
public class CustomConverter implements AttributeConverter<Status, String> {
#Override
public String convertToDatabaseColumn(Status attribute) {
return attribute.getValue() ;
}
#Override
public Status convertToEntityAttribute(String dbData) {
return StatusFirmaDocumento.fromString(dbData);
}
}
yeah, it's a shame that you can't tell to hibernate "translate DL to DELIVERED" and viceversa
In my datamodel a have many entities where attributes are mapped to enumerations like this:
#Enumerated(EnumType.STRING)
private MySpecialEnum enumValue;
MySpecialEnum defines some fixed values. The mapping works fine and if the database holds a NULL-value for a column I get NULL in the enumValue-attribute too.
The problem is, that my backend module (where I have no influence on) uses spaces in CHAR-columns to identify that no value is set. So I get an IllegalArgumentException instead of a NULL-value.
So my question is: Is there a JPA-Event where I can change the value read from the database before mapping to the enum-attribute?
For the write-access there is the #PrePersist where I can change Null-values to spaces. I know there is the #PostLoad-event, but this is handled after mapping.
Btw: I am using OpenJpa shipped within WebSphere Application Server.
You could map the enum-type field as #Transient (it will not be persisted) and map another field directly as String, synchronizing them in #PostLoad:
#Transient
private MyEnum fieldProxy;
private String fieldDB;
#PostLoad
public void postLoad() {
if (" ".equals(fieldDB))
fieldProxy = null;
else
fieldProxy = MyEnum.valueOf(fieldDB);
}
Use get/setFieldProxy() in your Java code.
As for synchronizing the other way, I'd do it in a setter, not in a #PreUpdate, as changes to #Transient fields probably do not mark the entity as modified and the update operation might not be triggered (I'm not sure of this):
public void setFieldProxy(MyEnum value) {
fieldProxy = value;
if (fieldProxy == null)
fieldDB = " ";
else
fieldDB = value.name();
}
OpenJPA offers #Externalizer and #Factory to handle "special" database values.
See this: http://ci.apache.org/projects/openjpa/2.0.x/manual/manual.html#ref_guide_pc_extern_values
You might end up with something like this: not tested...
#Factory("MyClass.mySpecialEnumFactory")
private MySpecialEnum special;
...
public static MySpecialEnum mySpecialEnumFactory(String external) {
if(StringUtils.isBlank(external) return null; // or why not MySpecialEnum.NONE;
return MySpecialEnum.valueOf(external);
}
Is it possible for a JPA entity class to contain two embedded (#Embedded) fields? An example would be:
#Entity
public class Person {
#Embedded
public Address home;
#Embedded
public Address work;
}
public class Address {
public String street;
...
}
In this case a Person can contain two Address instances - home and work. I'm using JPA with Hibernate's implementation. When I generate the schema using Hibernate Tools, it only embeds one Address. What I'd like is two embedded Address instances, each with its column names distinguished or pre-pended with some prefix (such as home and work). I know of #AttributeOverrides, but this requires that each attribute be individually overridden. This can get cumbersome if the embedded object (Address) gets big as each column needs to be individually overridden.
The generic JPA way to do it is with #AttributeOverride. This should work in both EclipseLink and Hibernate.
#Entity
public class Person {
#AttributeOverrides({
#AttributeOverride(name="street",column=#Column(name="homeStreet")),
...
})
#Embedded public Address home;
#AttributeOverrides({
#AttributeOverride(name="street",column=#Column(name="workStreet")),
...
})
#Embedded public Address work;
}
#Embeddable public class Address {
#Basic public String street;
...
}
}
If you want to have the same embeddable object type twice in the same entity, the column name defaulting will not work: at least one of the columns will have to be explicit. Hibernate goes beyond the EJB3 spec and allows you to enhance the defaulting mechanism through the NamingStrategy. DefaultComponentSafeNamingStrategy is a small improvement over the default EJB3NamingStrategy that allows embedded objects to be defaulted even if used twice in the same entity.
From Hibernate Annotations Doc: http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#d0e714
When using Eclipse Link, an alternative to using AttributeOverrides it to use a SessionCustomizer. This solves the issue for all entities in one go:
public class EmbeddedFieldNamesSessionCustomizer implements SessionCustomizer {
#SuppressWarnings("rawtypes")
#Override
public void customize(Session session) throws Exception {
Map<Class, ClassDescriptor> descriptors = session.getDescriptors();
for (ClassDescriptor classDescriptor : descriptors.values()) {
for (DatabaseMapping databaseMapping : classDescriptor.getMappings()) {
if (databaseMapping.isAggregateObjectMapping()) {
AggregateObjectMapping m = (AggregateObjectMapping) databaseMapping;
Map<String, DatabaseField> mapping = m.getAggregateToSourceFields();
ClassDescriptor refDesc = descriptors.get(m.getReferenceClass());
for (DatabaseMapping refMapping : refDesc.getMappings()) {
if (refMapping.isDirectToFieldMapping()) {
DirectToFieldMapping refDirectMapping = (DirectToFieldMapping) refMapping;
String refFieldName = refDirectMapping.getField().getName();
if (!mapping.containsKey(refFieldName)) {
DatabaseField mappedField = refDirectMapping.getField().clone();
mappedField.setName(m.getAttributeName() + "_" + mappedField.getName());
mapping.put(refFieldName, mappedField);
}
}
}
}
}
}
}
}
In case you are using hibernate you can also use a different naming scheme which adds unique prefixes to columns for identical embedded fields. See Automatically Add a Prefix to Column Names for #Embeddable Classes
What is the smartest way to get an entity with a field of type List persisted?
Command.java
package persistlistofstring;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Persistence;
#Entity
public class Command implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Basic
List<String> arguments = new ArrayList<String>();
public static void main(String[] args) {
Command command = new Command();
EntityManager em = Persistence
.createEntityManagerFactory("pu")
.createEntityManager();
em.getTransaction().begin();
em.persist(command);
em.getTransaction().commit();
em.close();
System.out.println("Persisted with id=" + command.id);
}
}
This code produces:
> Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named pu: Provider named oracle.toplink.essentials.PersistenceProvider threw unexpected exception at create EntityManagerFactory:
> oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException
> Local Exception Stack:
> Exception [TOPLINK-30005] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException
> Exception Description: An exception was thrown while searching for persistence archives with ClassLoader: sun.misc.Launcher$AppClassLoader#11b86e7
> Internal Exception: javax.persistence.PersistenceException: Exception [TOPLINK-28018] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.EntityManagerSetupException
> Exception Description: predeploy for PersistenceUnit [pu] failed.
> Internal Exception: Exception [TOPLINK-7155] (Oracle TopLink Essentials - 2.0.1 (Build b09d-fcs (12/06/2007))): oracle.toplink.essentials.exceptions.ValidationException
> Exception Description: The type [interface java.util.List] for the attribute [arguments] on the entity class [class persistlistofstring.Command] is not a valid type for a serialized mapping. The attribute type must implement the Serializable interface.
> at oracle.toplink.essentials.exceptions.PersistenceUnitLoadingException.exceptionSearchingForPersistenceResources(PersistenceUnitLoadingException.java:143)
> at oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider.createEntityManagerFactory(EntityManagerFactoryProvider.java:169)
> at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:110)
> at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:83)
> at persistlistofstring.Command.main(Command.java:30)
> Caused by:
> ...
Use some JPA 2 implementation: it adds a #ElementCollection annotation, similar to the Hibernate one, that does exactly what you need. There's one example here.
Edit
As mentioned in the comments below, the correct JPA 2 implementation is
javax.persistence.ElementCollection
#ElementCollection
Map<Key, Value> collection;
See: http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html
Should anyone be looking for an alternative solution where you store your string lists as one field in your database, here's how I solved that. Create a Converter like this:
import java.util.Arrays;
import java.util.List;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import static java.util.Collections.*;
#Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
private static final String SPLIT_CHAR = ";";
#Override
public String convertToDatabaseColumn(List<String> stringList) {
return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";
}
#Override
public List<String> convertToEntityAttribute(String string) {
return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList();
}
}
Now use it on your Entities like this:
#Convert(converter = StringListConverter.class)
private List<String> yourList;
In the database, your list will be stored as foo;bar;foobar, and in your Java object you will get a list with those strings.
It seems none of the answers explored the most important settings for an #ElementCollection mapping.
When you map a list with this annotation and let JPA/Hibernate auto-generate the tables, columns, etc., it'll use auto-generated names as well.
So, let's analyze a basic example:
#Entity
#Table(name = "sample")
public class MySample {
#Id
#GeneratedValue
private Long id;
#ElementCollection // 1
#CollectionTable(name = "my_list", joinColumns = #JoinColumn(name = "id")) // 2
#Column(name = "list") // 3
private List<String> list;
}
The basic #ElementCollection annotation (where you can define the known fetch and targetClass preferences)
The #CollectionTable annotation is very useful when it comes to giving a name to the table that'll be generated, as well as definitions like joinColumns, foreignKey's, indexes, uniqueConstraints, etc.
#Column is important to define the name of the column that'll store the varchar value of the list.
The generated DDL would be:
-- table sample
CREATE TABLE sample (
id bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (id)
);
-- table my_list
CREATE TABLE IF NOT EXISTS my_list (
id bigint(20) NOT NULL,
list varchar(255) DEFAULT NULL,
FOREIGN KEY (id) REFERENCES sample (id)
);
This answer was made pre-JPA2 implementations, if you're using JPA2, see the ElementCollection answer above:
Lists of objects inside a model object are generally considered "OneToMany" relationships with another object. However, a String is not (by itself) an allowable client of a One-to-Many relationship, as it doesn't have an ID.
So, you should convert your list of Strings to a list of Argument-class JPA objects containing an ID and a String. You could potentially use the String as the ID, which would save a little space in your table both from removing the ID field and by consolidating rows where the Strings are equal, but you would lose the ability to order the arguments back into their original order (as you didn't store any ordering information).
Alternatively, you could convert your list to #Transient and add another field (argStorage) to your class that is either a VARCHAR() or a CLOB. You'll then need to add 3 functions: 2 of them are the same and should convert your list of Strings into a single String (in argStorage) delimited in a fashion that you can easily separate them. Annotate these two functions (that each do the same thing) with #PrePersist and #PreUpdate. Finally, add the third function that splits the argStorage into the list of Strings again and annotate it #PostLoad. This will keep your CLOB updated with the strings whenever you go to store the Command, and keep the argStorage field updated before you store it to the DB.
I still suggest doing the first case. It's good practice for real relationships later.
We can also use this.
#Column(name="arguments")
#ElementCollection(targetClass=String.class)
private List<String> arguments;
According to Java Persistence with Hibernate
mapping collections of value types with annotations [...]. At the time of writing it isn't part of the Java Persistence standard
If you were using Hibernate, you could do something like:
#CollectionOfElements(targetElement = String.class)
#JoinTable(name = "foo", joinColumns = #JoinColumn(name = "foo_id"))
#IndexColumn(name = "POSITION", base = 1)
#Column(name = "baz", nullable = false)
private List<String> arguments = new ArrayList<String>();
Update: Note, this is now available in JPA2.
When using the Hibernate implementation of JPA , I've found that simply declaring the type as an ArrayList instead of List allows hibernate to store the list of data.
Clearly this has a number of disadvantages compared to creating a list of Entity objects. No lazy loading, no ability to reference the entities in the list from other objects, perhaps more difficulty in constructing database queries. However when you are dealing with lists of fairly primitive types that you will always want to eagerly fetch along with the entity, then this approach seems fine to me.
#Entity
public class Command implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
ArrayList<String> arguments = new ArrayList<String>();
}
I had the same problem so I invested the possible solution given but at the end I decided to implement my ';' separated list of String.
so I have
// a ; separated list of arguments
String arguments;
public List<String> getArguments() {
return Arrays.asList(arguments.split(";"));
}
This way the list is easily readable/editable in the database table;
Ok i know its bit late. But for those brave souls that will see this as time passes.
As written in documentation:
#Basic:
The simplest type of mapping to a database column. The Basic annotation can be applied to a persistent property or instance variable of any of the following types: Java primitive types, [...], enums, and any other type that implements java.io.Serializable.
The important part is type that implements Serializable
So by far the most simple and easiest to use solution is simply using ArrayList instead of List (or any serializable container):
#Basic
ArrayList<Color> lovedColors;
#Basic
ArrayList<String> catNames;
Remember however that this will use system serialization, so it will come with some price, such as:
if serialized object model will change, u might not be able to restore data
small overhead is added for each element stored.
In short
it is quite simple to store flags or few elements, but i would not
recomend it to store data that might grow big.
Here is the solution for storing a Set using #Converter and StringTokenizer. A bit more checks against #jonck-van-der-kogel solution.
In your Entity class:
#Convert(converter = StringSetConverter.class)
#Column
private Set<String> washSaleTickers;
StringSetConverter:
package com.model.domain.converters;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
#Converter
public class StringSetConverter implements AttributeConverter<Set<String>, String> {
private final String GROUP_DELIMITER = "=IWILLNEVERHAPPEN=";
#Override
public String convertToDatabaseColumn(Set<String> stringList) {
if (stringList == null) {
return new String();
}
return String.join(GROUP_DELIMITER, stringList);
}
#Override
public Set<String> convertToEntityAttribute(String string) {
Set<String> resultingSet = new HashSet<>();
StringTokenizer st = new StringTokenizer(string, GROUP_DELIMITER);
while (st.hasMoreTokens())
resultingSet.add(st.nextToken());
return resultingSet;
}
}
Thiago answer is correct, adding sample more specific to question, #ElementCollection will create new table in your database, but without mapping two tables, It means that the collection is not a collection of entities, but a collection of simple types (Strings, etc.) or a collection of embeddable elements (class annotated with #Embeddable).
Here is the sample to persist list of String
#ElementCollection
private Collection<String> options = new ArrayList<String>();
Here is the sample to persist list of Custom object
#Embedded
#ElementCollection
private Collection<Car> carList = new ArrayList<Car>();
For this case we need to make class Embeddable
#Embeddable
public class Car {
}
As my reputation is not enough yet to comment on the much underrated answer written by #razvang:
As this question was asked over a decade ago, keep in mind much of the world has changed in the time since. We now have databases with native JSON column support and can use this functionality instead of using separate entities, joins or custom String-to-List converters, which are used by the other answers.
Let me suggest two purely optional changes to #razvang's superb answer though, which might be interesting depending on your specific situation:
You could omit the auto_apply = true and add #Convert(converter = <CONVERTER_CLASS_NAME>.class) to the entity field to keep control over when your converter is used.
Instead of throwing a RuntimeException whenever a conversion fails, you could handle the error right there (for example pass an empty list and write a log message) to make it fail somewhat gracefully.
What I wanted was a simple way of persisting a set of Strings, in a table column.
I ended up using JSON, as MySQL 5.7+, has native support.
Here's my solution
#Column(name = "eligible_approvers", columnDefinition = "json")
#Convert(converter = ArrayJsonConverter.class)
private Set<String> eligibleApprovers;
And then write a very basic converter
#Converter(autoApply = true)
public class ArrayJsonConverter implements AttributeConverter<Set, String> {
static final ObjectMapper mapper = new ObjectMapper();
#Override
public String convertToDatabaseColumn(Set list) {
if (list == null)
return null;
try {
return mapper.writeValueAsString(list);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
#Override
public Set convertToEntityAttribute(String dbJson) {
if (dbJson == null)
return null;
try {
return mapper.readValue(dbJson, new TypeReference<Set<String>>() {
});
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
My fix for this issue was to separate the primary key with the foreign key. If you are using eclipse and made the above changes please remember to refresh the database explorer. Then recreate the entities from the tables.