How to persist an enum with JPA? - java

I'm using MySQL and JPA.
I have an enum that has it's own table.
I have a field in an entity (entity1) that uses this enum. This field is annotated with: #Enumeration(EnumType.STRING).
1 - is it correct to persist this field in entity1 as a column in the db when it has it's own table?
2 - if I am using #JsonProperty on my other fields and answer to 1 is "yes", must I use #JsonProperty on the enum field too?
3 - what's the point in having the enum in a separate table?
Currently, with just the #Enumeration annotation and a column for the enum for entity1 I get error: was annotated as enumerated, but its java type is not an enum

1- How I addressed similar problem was as follows :
I defined the enum in a separate entity :
#Entity
#Table(name="CALC_METHOD")
public class CalculationMethod {
public CalculationMethod() {
super();
// TODO Auto-generated constructor stub
}
#Id
#Enumerated(EnumType.STRING)
#Column(name="METHOD_NAME")
private CalculationMethodId calcMethodID;
#Column(name="DISPLAY_TEXT")
private String displayName;
.
.
.
.
then I refered to it in another entity as follows :
#OneToOne
#JoinColumn(name="CALCULATION_METHOD",referencedColumnName="METHOD_NAME")
private CalculationMethod calculationMethod;
that way it's stored in a seprate table, yet referenced from another entity with no duplication ... the point here is that you can't map enum scoped variables so when I needed to store a display name for the enum value, I needed to make it a separate attribute as you see
3- why to store it in table? because from the java POV it was really an enum , and I want to apply some calculation methods polymorpically (like calculate some value in the refering entity using the calculation method, so I defined calculate() method for each calculation method , each with a different implementation then call it while calculating a whole) the I wanted it to be always read with the same value and display name from many places in the code , and If I want to modify the display name, it's done only # one place -thus consistency and maintainability-
2- it depends on the requirement and your json model

For your situation I normaly use an entity on BBDD for the ENUM like:
AuthenticationType
id, name, value : (0, CERT, Certificate)
Where name is the real ENUM and value is the text I want to represent on the views.
For that you need the following:
public enum AuthenticationTypeEnum{
CERT, PASS;
}
#Entity
#Table(name = "AuthenticationType")
public class AuthenticationType{
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
#Enumerated(EnumType.STRING)
private AuthenticationTypeEnum name; // REAL ENUM TYPE
#Column(name = "VALUE")
private String value;
....
}
#Entity...
class Authentication{
private String login;
...
#ManyToOne
private AuthenticationType type; // ENUM USE
...
}
In that way you can edit the value of your ENUM on BBDD without changing your code, for me this is one of the best options.
Hope this helps.
When you persist the entity use the cascade all on JPA to persist also the enum entity.
NOTE: On normal situations, the enums not change, so you set them only ones. They are a prerequisite to the application so they change on rare circumstances.

Related

How to get key generated by Hibernate when I map a collection with #collectionId?

I am working on Quiz management. All mapping is done by Hibernate annotation for question to options. Question is an entity whereas all options are embedded object so I mapped option as follows :
QuestionMasterDTO's mapping for TabkidsMCQOptionMasterDTO :
#ElementCollection(fetch=FetchType.EAGER,targetClass=TabkidsMCQOptionMasterDTO.class)
#Fetch(FetchMode.SUBSELECT)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
#CollectionTable(name="TABKIDS_MCQ_OPTION_MASTER",joinColumns={#JoinColumn(name="TMOM_QUESTION_ID")})
#GenericGenerator(name="hilo-gen",strategy="hilo")
#CollectionId(columns={#Column(name="TMOM_ID")},generator="hilo-gen", type=#Type(type="long"))
public Collection<IOptionMaster> getOptions() {
return options;
}
Where TabkidsMCQOptionMasterDTO is :
#Embeddable
public class TabkidsMCQOptionMasterDTO implements IOptionMaster {
private String optionText;
private boolean correct;
#Column(name = "TMOM_OPTION_TEXT")
public String getOptionText() {
return optionText;
}
#Column(name = "TMOM_IS_CORRECT")
public boolean isCorrect() {
return correct;
}
//setters omitted
}
Now in above mapping you can see I am using a generator i.e. hilo-gen and assigning a unique id to every option available in collection and that column name is 'TMOM_ID'.
This line :
#GenericGenerator(name="hilo-gen",strategy="hilo")
#CollectionId(columns={#Column(name="TMOM_ID")},generator="hilo-gen", type=#Type(type="long"))
Now when I fetch a question from database by using Hibernate criteria I am getting all options associated with the question but not getting unique option id i.e. TMOM_ID. How to get this id ??
Hibernate mainly uses two type of mapping Entity Type and Value Type.
Entity type means It will have its own existence in the world i.e. It must have a primary key.
Whereas Value type don't have its own existence this means value type always dependent on Entity type.
As your problem I can see Option does not have its won existence because it must always dependent of Question which is an entity .
So from my point of view if you want to access Option Id, Option must also be an entity type this means You have to use #Entity on top of TabkidsMCQOptionMasterDTO rather than making it as #Embeddable.
So here you have to use #OneToMany in your question master and from Other side in TabkidsMCQOptionMasterDTO you have to use #ManyToOne mapping.
I hope this will help to achieve what you want to get.

JPA Embeddeble PK and nullable field

I have a table called "Attributes" which has a PK of 3 fields which can be null. In this case Style_no is not null but item_no and size_no are null.
Is it possible to have a Embeddeble PK where fields can be null?
#Entity
#Table(name="ATTRIBUTE")
public class Attribute {
#EmbeddedId
private AttributePK attrPK;
...
#Embeddable
public static class AttributePK implements Serializable{
private static final long serialVersionUID = -2976341677484364274L;
#Column(name="STYLE_NO", nullable=true)
protected String styleNo;
#Column(name="ITEM_NO", nullable=true)
protected String itemNo;
#Column(name="SIZE_NO", nullable=true)
protected String sizeNo;
...
When i try to reference over one field e.g. style_no the result amount is 0.
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="attrPK.styleNo")
#MapKey(name="attrPK.name")
public Map<String,Attribute> attributesX;
OR
#OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL, orphanRemoval=true)
#JoinColumn(name="STYLE_NO", referencedColumnName="STYLE_NO")
private List<Attribute> attributes;
When i remove item_no and size_no as pk im receiving a valid result.
Edit:
To make my question more specific. Is per JPA guideline or "common sense" not allowed to use nullable fields for EmbeddebedId? If not, what annotions or logic do i need to add to make it work without adding another PK?
Once filling the nullable field in the PK with values. The result is corrct.
Thank you very much!
Since you must not use null in your PK, this is what you should do:
Add a surrogate primary key.
You can still achieve the uniqueness constraint and the default PK index with a parial index (in PostgreSQL):
CREATE UNIQUE INDEX style_item_size_idx ON my_table (Style_no, item_no, size_no) WHERE (item_no IS NOT NULL AND size_no IS NOT NULL);
Take a look here
Seems, that answer that (NULL == NULL) -> false is about your question.
Usually a compound pk is not allowed to have null-values in the database.
Technically we would say: Why not, a null can be a value too.
The DB-Analyst would ask: Ok, if i sort 1,2,3,4,null,5. Would you say null is before 1 or after 5?
Therefore PostgreSQL, Oracle, MySQL will not support null-values in compound primary keys.
JPA guideline or "common sense" not allowed to use nullable fields for EmbeddebedId? If not, what annotions or logic do i need to add to make it work without adding another PK?
answer is as below
#EmbeddedId
private AttributePK attrPK;
does not allow the primary key to be null.
so to make it happen use another annotation like below
#IdClass(AttributePK.class)
private AttributePK attrPK;

Encapsulating the returning of only some of that data from Hibernate query

I have a POJO that maps to a row in a specific table. The row describes an image in some site and contains data like width, height, url, some sort of status and some more fields. In some legacy code I have a query (in hibernate) that returns the url and the status. This data is encapsulated in a class ImageStatusAndOrigFilename.
I think this is a bad idea because:
What if tomorrow I need to query for some other fields? The name is too coupled to the data.
In the past the only way to get the image width and height was to parse the url. Today we map the width and height in the db and thus I now need to get the image size and status (and I don't care anymore about the original file name). So I need to change this class but can't because it's being used in other places in the code. I wish to get to something more generic that is not coupled to a specific scenario and can be extended when needed.
I'm trying to figure out which data structure to use. Should I use the original POJO that has all the fields but leave some of them null (I don't want to query all of the fields as I don't need all of them in this scenario). Should I create another POJO for this specific query (with a better name of course)?
Any other suggestions are surely welcome as well.
EDIT:
The POJO:
#Entity
#Table(name = "web_image")
public class WebImage {
private long id;
private Document document;
private Integer mediaType;
private Integer width;
private Integer height;
private Date creationDate;
private Date modificationDate;
private String origUrl;
private ImageStatus status;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
public Long getId() {
return id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "document_id")
public Document getDocument() {
return document;
}
public void setDocument(final OBDocument document) {
this.document = document;
}
#Column(name = "width")
public Integer getWidth() {
return width;
}
public void setWidth(final Integer width) {
this.width = width;
}
// Other getters and setters for the rest of the private fields
}
The query:
SELECT b.document_id , b.status , b.orig_file_id, a.min_id as id FROM web_image b,
( SELECT x.document_id, MAX(x.id) max_id, MIN(x.id) min_id
FROM web_image x
WHERE x.document_id in ( :docs ) GROUP BY x.document_id) a
WHERE a.max_id = b.id
What about this:
#MappedSuperclass
public class ImageStatusAndOrigFilename {
...
}
#Entity
public class WebImage extends ImageStatusAndOrigFilename {
...
}
Now you have both classes, the old one is not an entity, but its clients don't know anything about it, and all fetching and persistings are on WebImage class, but you can query for ImageStatusAndOrigFilename.
If it is really necessary to avoid loading unused columns - and you would need to profile to determine if any saving is actually worth it - then a simple solution is simply to write a query using a JPA Contructor Expression.
JPQL:
http://docs.oracle.com/html/E24396_01/ejb3_langref.html#ejb3_langref_constructor
Criteria API:
http://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/querycriteria.html#querycriteria-typedquery-multiselect
Problem with these is you need to add a constructor for each set of properties.
Hibernate specific option - no Constructors required:
See Transformers.aliasToBean:
Java - Hibernate criteria.setResultTransformer() initializes model fields with default values
For all of these options you can use some other DTO or use your existing Entity i.e. return an unmanaged instance.
Lazy Loading
You can also lazy load fields however that required byte code manipulation:
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html#performance-fetching-lazyproperties
Also note the comments:
Hibernate3 supports the lazy fetching of individual properties. This
optimization technique is also known as fetch groups. Please note
that this is mostly a marketing feature; optimizing row reads is much
more important than optimization of column reads. However, only
loading some properties of a class could be useful in extreme cases.

Best practice for storing global data in J2EE App using Hibernate

I'm looking for the best solution to store Java EE application's global data using Hibernate. It will consist of key value pairs. Example:
projectStarted = "10-11-11"
developerNumber = 3
teamLeader = "John"
As you see, all of this entries have different types.
For now I see two options:
1) Create GlobalData entity. Each field of it will be represented as unique column in the table and will contain unique setting. This way I have no problems with type casting, but I would like to avoid it in case where there will be big amount of settings.
2) Create Setting entity. Each of it will contain two fields: key(Primary key) and value and will be represented as unique record in the table. This is preferable solution, but It's seems to me that I will get a lot of type casting, because settings can be any type.
So basically, I'm looking for the way to implement second solution without getting a lot of troubles from different types. Can anybody help me?
Thanks.
Edit 1.
Yeah, thanks Christian. Just got similar idea.
What if I will have Settings entity, which will be like:
#Entity
#Table(name = "settings")
public class Setting {
#Column
private String key;
#Column
private String value;
#Column
private String converterClassFullName; //example by.lugovsky.MyConverter
//Getters, setters
}
And GlobalData class.
public class GlobalData {
private Date projectStarted;
private int developerNumber;
private String teamLeader;
Set<Setting> settings;
//Getters and setters for all, except settings.
}
So basically my idea is to convert Setting entity before persisting/updating/ etc. I can do this in my DAO, but I was wondering, if I could annotate GlobalData class with #Entity annotation as well without creating new table. This way I can set OneToMany annotation to Setting's set and Perform conversions in the internal #PrePersist etc. methods.
Will Hibernate allow me to do this?
Thanks again
You could store a Converter-Class into the db and the let it run through the given converter for a property before using the value. JSF offers Converter API:
public interface Converter{
public Object getAsObject(FacesContext fc, UIComponent component, String value) throws ConverterException;
public String getAsString(FacesContext fc, UIComponent component, Object obj) throws ConverterException;
}
If you have a schema with
name: String
value: String
converter: Class
then you could do something like this:
PropertyEntry pe = // Get from OR-Mapper
Converter c = (Converter) pe.getConverter().newInstance();
Object o = c.getAsObject(null, null, pe.getValue());
// use the object o instead of value
For even more coolness you could also define a field in the class which will not be persisted which you could use to hold the converted value within the object.

JPA map collection of Enums

Is there a way in JPA to map a collection of Enums within the Entity class? Or the only solution is to wrap Enum with another domain class and use it to map the collection?
#Entity
public class Person {
public enum InterestsEnum {Books, Sport, etc... }
//#???
Collection<InterestsEnum> interests;
}
I am using Hibernate JPA implementation, but of course would prefer implementation agnostic solution.
using Hibernate you can do
#ElementCollection(targetElement = InterestsEnum.class)
#JoinTable(name = "tblInterests", joinColumns = #JoinColumn(name = "personID"))
#Column(name = "interest", nullable = false)
#Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
The link in Andy's answer is a great starting point for mapping collections of "non-Entity" objects in JPA 2, but isn't quite complete when it comes to mapping enums. Here is what I came up with instead.
#Entity
public class Person {
#ElementCollection(targetClass=InterestsEnum.class)
#Enumerated(EnumType.STRING) // Possibly optional (I'm not sure) but defaults to ORDINAL.
#CollectionTable(name="person_interest")
#Column(name="interest") // Column name in person_interest
Collection<InterestsEnum> interests;
}
tl;dr A short solution would be the following:
#ElementCollection(targetClass = InterestsEnum.class)
#CollectionTable
#Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
The long answer is that with this annotations JPA will create one table that will hold the list of InterestsEnum pointing to the main class identifier (Person.class in this case).
#ElementCollections specify where JPA can find information about the Enum
#CollectionTable create the table that hold relationship from Person to InterestsEnum
#Enumerated(EnumType.STRING) tell JPA to persist the Enum as String, could be EnumType.ORDINAL
I was able to accomplish this in this simple way:
#ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;
Eager loading is required in order to avoid lazy loading inizializing error as explained here.
I'm using a slight modification of java.util.RegularEnumSet to have a persistent EnumSet:
#MappedSuperclass
#Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>>
extends AbstractSet<E> {
private long elements;
#Transient
private final Class<E> elementType;
#Transient
private final E[] universe;
public PersistentEnumSet(final Class<E> elementType) {
this.elementType = elementType;
try {
this.universe = (E[]) elementType.getMethod("values").invoke(null);
} catch (final ReflectiveOperationException e) {
throw new IllegalArgumentException("Not an enum type: " + elementType, e);
}
if (this.universe.length > 64) {
throw new IllegalArgumentException("More than 64 enum elements are not allowed");
}
}
// Copy everything else from java.util.RegularEnumSet
// ...
}
This class is now the base for all of my enum sets:
#Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
public InterestsSet() {
super(InterestsEnum.class);
}
}
And that set I can use in my entity:
#Entity
public class MyEntity {
// ...
#Embedded
#AttributeOverride(name="elements", column=#Column(name="interests"))
private InterestsSet interests = new InterestsSet();
}
Advantages:
Working with a type safe and performant enum set in your code (see java.util.EnumSet for a description)
The set is just one numeric column in the database
everything is plain JPA (no provider specific custom types)
easy (and short) declaration of new fields of the same type, compared with the other solutions
Drawbacks:
Code duplication (RegularEnumSet and PersistentEnumSet are nearly the same)
You could wrap the result of EnumSet.noneOf(enumType) in your PersistenEnumSet, declare AccessType.PROPERTY and provide two access methods which use reflection to read and write the elements field
An additional set class is needed for every enum class that should be stored in a persistent set
If your persistence provider supports embeddables without a public constructor, you could add #Embeddable to PersistentEnumSet and drop the
extra class (... interests = new PersistentEnumSet<>(InterestsEnum.class);)
You must use an #AttributeOverride, as given in my example, if you've got more than one PersistentEnumSet in your entity (otherwise both would be stored to the same column "elements")
The access of values() with reflection in the constructor is not optimal (especially when looking at the performance), but the two other options have their drawbacks as well:
An implementation like EnumSet.getUniverse() makes use of a sun.misc class
Providing the values array as parameter has the risk that the given values are not the correct ones
Only enums with up to 64 values are supported (is that really a drawback?)
You could use BigInteger instead
It's not easy to use the elements field in a criteria query or JPQL
You could use binary operators or a bitmask column with the appropriate functions, if your database supports that
Collections in JPA refer to one-to-many or many-to-many relationships and they can only contain other entities. Sorry, but you'd need to wrap those enums in an entity. If you think about it, you'd need some sort of ID field and foreign key to store this information anyway. That is unless you do something crazy like store a comma-separated list in a String (don't do this!).

Categories

Resources