JPA Multiple Embedded fields - java

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

Related

Dynamic generate GraphQL schema supports

Is it possible to dynamically create a GraphQL schema ?
We store the data in mongoDB and there is a possibility of new fields getting added. We do not want any code change to happen for this newly added field in the mongoDB document.
Is there any way we can generate the schema dynamically ?
Schema is defined in code, but for java(schema as pojo), when new
attribute is added, you have to update and recompile code, then
archive and deploy the jar again. Any way to generate schema by the
data instead of pre-define it?
Currently we are using java related projects (graphql-java, graphql-java-annotations) for GraphQL development.
You could use graphql-spqr, it allows you auto-generate a schema based on your service classes. In your case, it would look like this:
public class Pojo {
private Long id;
private String name;
// whatever Ext is, any (complex) object would work fine
private List<Ext> exts;
}
public class Ext {
public String something;
public String somethingElse;
}
Presumably, you have a service class containing your business logic:
public class PojoService {
//this could also return List<Pojo> or whatever is applicable
#GraphQLQuery(name = "pojo")
public Pojo getPojo() {...}
}
To expose this service, you'd just do the following:
GraphQLSchema schema = new GraphQLSchemaGenerator()
                .withOperationsFromSingleton(new PojoService())
                .generate();
You could then fire a query such as:
query test {
pojo {
id
name
exts {
something
somethingElse
} } }
No need for strange wrappers or custom code of any kind, nor sacrificing type safety. Works with generics, dependency injection, or any other jazz you may have in your project.
Full disclosure: I'm the author of graphql-spqr.
After some days' investigation. I found it is hard to generate schema dynamically in Java (or cost is so high).
Well, from another way. I think we can use Map as a compromised way to accomplish that.
POJO/Entity
public class POJO{
#GraphQLField
private Long id;
#GraphQLField
private String name;
// ...
#GraphQLField
private GMap exts;
}
GMap is a customized Map (Because Map/HashMap is a JDK inner class which could not make as GraphQL Schema but only extend).
GMap
public class GMap extends HashMap<String, String> {
#GraphQLField
public String get(#GraphQLName("key") String key) {
return super.get(key);
}
}
Retrieve data from Client
// query script
query test
{
your_method
{
id
name
exts {
get(key: "ext") // Add a extended attribute someday
}
}
}
// result
{
"errors":[],
"data":
{
"list":
[
{"id":1, name: "name1", exts: {"get": "ext1"}},
{"id":2, name: "name2", exts: {"get": "ext2"}}
]
}
}

Exposing JPA Entities to the Presentation Layer

It seems almost trivial to use JPA entities in the presentation layer.
XHTML:
<h:outputText value="#{catalog.item.name}:"/>
Controller:
#SessonScoped
#ManagedBean
class Catalog {
#EJB
private Item item = null;
public Item getItem(){
...
}
....
}
Entity JPA:
#Entity
class Item {
#Id
private Integer identifier;
#Column
private String name;
//gets and sets
}
Are there any restrictions for medium/large systems? Does it scale? Are there any gotchas?
JSF and Java EE experts such as Bauke Scholtz and Adam Bien recommend using Entities in the presentation layer as opposed to making some typically useless intermediary object.
I'm going to quote them below, but note that they sometimes use the term "DTO" (Data Transfer Object) to describe the intermediary object that some designs introduce between the Entities and presentation layer.
Adam Bien writes:
On the other hand considering a dedicated DTO layer as an investment, rarely pays off and often lead to over-engineered and bloated architectures. At the beginning the DTOs will be identical to your domain layer - without any impedance mismatch. If you are 'lucky', you will get few differences over the time. Especially with lightweight platforms like Java EE 6, the introduction of a DTO is a bottom-up, rather than top-down approach." ~ How evil are Data Transfer Objects
(Note that the above quoted article also suggests when it is appropriate to use an intermediary object: "It is perfectly valid to introduce a dedicated DTO to adapt an incompatible domain layer...")
Bien's description makes perfect sense to me. Having worked on projects where intermediary objects identical to the Entities are immediately introduced because "it's good design because it has low coupling" has been a huge, comical waste of time. It's possible to waste time converting DTOs to Entities, and it takes good team discipline to make sure developers treat the DTOs and Entities according to some project policy, e.g. on which objects do you perform validations, business logic, etc.
Bauke Scholtz writes:
However, for the average webapplication you don't need DTO's. You're already using JPA entities. You can just go ahead with using them in your JSF bean/view. This question already indicate that you don't need DTOs at all. You are not blocked by some specific business restrictions. You should not then search for design patterns so that you can apply it on your project. You should rather search for real problems in form of overcomplicated/unmaintainable code so that you can ask/find a suitable design pattern for it. ~ How to use DTO in JSF + Spring + Hibernate
JPA Entities are plain old java objects with decoupled states (DETACHED, MANAGED) and there would be no problem using them in the presentation layer.
In most applications it makes no sence to copy the fields of an JPA entity to an additional presentation object that provides the same state.
You could use JPA entities in combination with interfaces, so that your able to introduce additional transfer objects only if they are really needed and if no existing jpa entity matches the requirements of the target view.
For relations with interfaces the targetType attribute of #OneToMany, #OneToOne or #ManyToOne is needed (e. g. #OneToMany(targetType = SomeJPAEntity.class)).
Here is some example code for the Items entity used in both the persistence and the presentation layer of a Java FX application:
// Service definition for obtaining IItem objects.
public interface IItemService {
IItem getItemById(Integer id);
IItemWithAdditionalState getItemWithAdditionalStateById(Integer id);
}
// Definition of the item.
public interface IItem {
StringProperty nameProperty();
ObservableList subItems();
List getSubItems();
}
// Definition for the item with additional state.
public interface IItemWithAdditionalState extends IItem {
String getAdditionalState();
}
// Represents a sub item used in both the persistence and the presentation layer.
#Table(name = SubItem.TABLE_NAME)
#Entity
public class SubItem extends AbstractEntity implements ISubItem, Serializable {
public static final String TABLE_NAME = "SUB_ITEM";
}
// Represents an item used in both the persistence and the presentation layer.
#Access(AccessType.PROPERTY)
#Table(name = Item.TABLE_NAME)
#Entity
public class Item extends AbstractEntity implements IItem, Serializable {
public static final String TABLE_NAME = "ITEM";
private StringProperty nameProperty;
private String _name; // Shadow field for lazy loading of java fx properties.
private ObservableList<ISubItem> subItems = FXCollections.observableArrayList();
public StringProperty nameProperty() {
if (null == nameProperty) {
nameProperty = new SimpleStringProperty(this, "name");
}
return nameProperty;
}
public String getName() {
return null == nameProperty ? _name : nameProperty.get();
}
public void setName(String name) {
if (null == nameProperty) {
_name = name;
} else {
_name = null;
nameProperty.set(name);
}
}
#Override
public ObservableList<ISubItem> subItems() {
return subItems;
}
#JoinColumn(name = "ID")
#OneToMany(targetEntity = SubItem.class)
#Override
public List<ISubItem> getSubItems() {
return subItems;
}
public void setSubItems(List<ISubItem> subItems) {
this.subItems.setAll(subItems)
}
}
// New added presentation data transfer object for matching the requirements of a special view.
public class ItemWithAdditionalState extends Item implements IItemWithAdditionalState {
String getAdditionalState();
}
From a technical point of view, nothing stops you from querying your entities in your data layer and allowing them to flow all the way through the business tier and into your presentation layer. In fact, it's a plausible solution for very small scale applications or proofs of concept work.
The problem with that approach is that you now have a tightly coupled relationship between the domain entity classes and your view. Any change to your domain model immediately impacts your views or any view requirement changes could immediately impact your domain model. This tight coupling isn't desirable in any complex application.

Unindexing Embedded Class Attributes using JDO (app-engine)

I embedding a User Defined Class (StudentProfile) inside another class (Account) using the #Embedded and #EmbeddedOnly JDO Annotations. Google Datastore indexes the attributes of the Embedded class by default and I would like to unindex many of the attributes in the Embedded Class (StudentProfile).
I have tried using the '#Extension(vendorName="datanucleus", key="gae.unindexed", value="true")' in the Account Class, where I declare the StudentProfile Attribute, and at the various attributes of StudentProfile class itself. Even after this, I am able to filter by studentAttribute.shortName (a field I annotated to be unindexed). Since Google Datastore documentation says that unindexed fields cannot be used as filters, I take this to mean that the unindexing effort was in vain.
I believe there is an #Unindex annotation in objectify, is there an equivalent in JDO?
I also tried to experiment with unindexing a normal attribute in the Account class (using the JDO Extension) and it works (ie returns null when I try to filter by that attribute). Here is the relevant code from each class. Any help is greatly aprreciated! Thanks!
#PersistenceCapable
public class Account {
// primary key and other attributes...
// the embedded class with renaming for the clashing fields
#Persistent(dependent="true", defaultFetchGroup="false")
#Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
#Embedded(members = {
#Persistent(name="shortName", columns=#Column(name="shortName"), extensions=#Extension(vendorName="datanucleus", key="gae.unindexed", value="true")),
#Persistent(name="email", columns=#Column(name="personalEmail")),
#Persistent(name="institute", columns=#Column(name="originalInstitute"))
})
private StudentProfile studentProfile;
}
// the embeddedOnly class
#PersistenceCapable(embeddedOnly="true")
public class StudentProfile {
// other attributes...
// the filter attribute I used for querying
#Persistent
#Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
private String shortName;
}
public StudentProfileAttributes getStudentProfile(String shortName) {
Query q = getPM().newQuery(Account.class);
q.declareParameters("String shortNameParam");
q.setFilter("studentProfile.shortName == shortNameParam");
// the following execution works and a result is returned!
#SuppressWarnings("unchecked")
List<Account> accountsList = (List<Account>) q.execute(shortName);
}
There is a blog post that tells you all about such things. And more recent versions (2.x) of GAE JDO plugin have an #Unindexed (which is clearly not part of JDO, being GAE-specific).
http://gae-java-persistence.blogspot.co.uk/2009/11/unindexed-properties.html

Hibernate Enum mapping using annotaions

I have an existing database that I am now connecting to using hibernate. I cannot change the data in it at the moment and have everything working apart from a single column.
I have a status column that has the values:
new
mailed
in
out
And the column is mapped as follows:
#Column(name = "STATUS", nullable = false, length = 50)
#Enumerated(EnumType.STRING)
private TeamMemberStatus status;
I would REALLY like (for application reasons) to have this column mapped as a Java Enum (TeamMemberStatus), but due to the fact that 'new' is a keyword in Java I cannot have that as an enum member.
If I have the enum contstants NEW, MAILED, IN and OUT hibernate fails as inside EnumType it does a Enum.valueOf().
Is there any way for me to map this to my Enum without having to write a complex UserType?
-- added content
My Enum like this:
public enum TeamMemberStatus {
NEW, MAILED, IN, OUT
}
is a valid Java enum, but not matching the case of the database. If I change it to match the database like:
public enum TeamMemberStatus {
new, mailed, in, out
}
It won't compile as 'new' is a Java reserved word.
If you can use a SQL UPPER statement at database, It will work without using any UserType
UPDATE
Well, It can not be The nicest solution but it solves what you want
#Entity
public class WrapperEntity {
private TeamMemberStatus memberStatus;
#Transient
private TeamMemberStatus getMemberStatus() {
return this.memberStatus;
}
public void setMemberStatus(TeamMemberStatus memberStatus) {
this.memberStatus = memberStatus;
}
#Column(name="STATUS", nullable=false, length=50)
public String getMemberStatusAsString() {
return memberStatus.name().toLowerCase();
}
public void setMemberStatusAsString(String memberStatus) {
this.setsetMemberStatus(TeamMemberStatus.valueOf(memberStatus.toUpperCase()));
}
}
If your Database values are "new", "mailed", "in" and "out" then your Enum need exactly the same names. - I believe that the problem is, that your Enums are in capital letters but your data base values not.

How to persist a property of type List<String> in JPA?

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.

Categories