How to map Hashtable component of a class, using JPA? - java

I have this class:
public class DBRow {
public String url;
public String title;
public static Hashtable<String, Integer> words;
public Vector<String> keywords;
}
What I must do is store many instances of this class in a database. I'm using Hibernate and JPA to get the job done. Using JPA I've come so far (really not far):
#Entity
#Table(name="MAIN_TABLE")
public class DBRow {
#Column(name="url")
public String url;
#Column(name="title")
public String title;
public static Hashtable<String, Integer> words;
public Vector<String> keywords;
}
I want my database to have 3 tables - MAIN_TABLE - auto-icremented id as primary key, url and title; HASH - containing a key-value pair from the Hashtable<String, Integer> and id to refer to which instance of DBRow class it belongs (and also to relate to the MAIN_TABLE); VECTOR - pretty much the same story like HASH but with a Vector<String>. So what I'm asking is hot to map the hashtable and the vector, using JPA to get it done?? I've been trying to find a way to do this but haven't found one so far... or maybe I'm not on the right way! Any suggestions are welcome. Thank you in advance.

This is not possible with JPA 1.0 (at least, not with standard annotations) and since you did not mention your JPA provider, I will only cover JPA 2.0. In JPA 2.0, the #ElementCollection annotation can be used to map a collection of basic types or embeddable objects.
Below some examples taken from EclipseLink/Development/JPA 2.0/new collection mappings:
Example 1 - An element collection representing a basic collection mapping.
#ElementCollection
#Column(name="DESIGNATION")
// CollectionTable will default in this case, Entity name + "_" + attribute name
// JoinColumns will default in this case which are different from BasicCollection collection table default
private Collection<String> designations;
Example 2 - An element collection representing an basic map mapping.
#ElementCollection
#MapKeyColumn(name="Q_DATE")
#Column(name="QUOTE")
#CollectionTable(name="EXPERT_QUOTES", joinColumns=#JoinColumn(name="EBC_ID"))
public Map<Date, String> getQuotes() {
return quotes;
}
Now, I second one of the comment to your question and I would consider using "moderner" data structure like ArrayList and HashMap.
Also note that static (and final) entity fields are always considered to be transient i.e. can't be persisted.

Related

Returning and persisting a LinkedHashMap instead of Map in JPA Entity

I'm trying to persist a LinkedHashMap in my JPA Entity in a Spring Boot project of mine. I need this due to LinkedHashMap preserving insertion-order compared to other Map interface implementations.
In my Comic JPA Entity, I have the field:
#ElementCollection
#Column(name="price_history", nullable=true)
private Map<String, Float> priceHistory = new LinkedHashMap<String, Float>();
My constructor for the Comic entity takes a LinkedHashMap<String,Float> as a parameter as well.
My getter and setter methods look like this:
public Map<String,Float> getPriceHistory() {
return priceHistory;
}
public void setPriceHistory(Map<String,Float> priceHistory) {
this.priceHistory = priceHistory;
}
I have a method in my REST controller that allows me to edit the data for any Comic JPA entity instance. The following piece of code takes care of putting prices into the priceHistory Map<String,Float> object:
editComic.getPriceHistory().put(WordUtils.capitalizeFully(LocalDate.now().getMonth().toString()
+ " " + LocalDate.now().getYear()), price);
editComic.setPriceHistory(editComic.getPriceHistory());
Is there a way for me to persist LinkedHashMap<String,Float> in JPA? I've tried annotating the field with #Lob but it causes an exception in PostgreSQL:
org.postgresql.util.PSQLException: Large Objects may not be used in auto-commit mode.

JPA/Hibernate with Dynamic Object Model

I have a table with the number of column is variable (depend of customer),
so I looking for a way to mapping this table with a java object using JPA/Hibernate or other
I can not use POJO because it's limited to a stable table so I'd like to use java object like this
class MyObject {
int id;
Map<String, Object> fields = new Map<String, Object>();
public void setId(int id) {
this.id = id;
}
public void setField(String key, Object value) {
fields.put(key, value);
}
}
The table for storing MyObject :
TABLE MYTABLE (
ID INTEGER,
FIELD1 VARCHAR,
FIELD2 DATE,
FIELD3 INTEGER
)
MyObject myObject = new MyObject();
myObject.setId(id);
myObject.setField("FIELD1" , "hello world");
myObject.setField("FIELD2" , new Date());
myObject.setField("FIELD3" , 21)
Action to save myObject in db;
And of course the possibility to query
Hibernate supports what they refer to as Dynamic Models. See further the following link which notes:
Persistent entities do not necessarily have to be represented as
POJO/JavaBean classes. Hibernate also supports dynamic models (using
Maps of Maps at runtime). With this approach, you do not write
persistent classes, only mapping files.
http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#dynamic-model
Previous versions of the Hibernate docs give some more detail. See:
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#persistent-classes-dynamicmodels
There's an example here that seems (from a quick skim) to allow for combining normal POJO with dynamic fields.
https://www.infoq.com/articles/hibernate-custom-fields

Hibernate QBE doesn't work as expected

Here is the method I wrote
public static <T> List selectOnCriteria(SessionFactory sessionFactory, int maxResults, T model)
{
List resultList=null;
session=sessionFactory.openSession();
Criteria criteria= session.createCriteria(model.getClass());
criteria.add(Example.create(model).ignoreCase());// only for exact search, uppercase and lowercase are both included in ignorecase
if(maxResults>0)
criteria.setMaxResults(maxResults);
resultList=criteria.list();
return resultList;
}
Here is one of my models
public class UserModel
{
#Id
private String username;
private String password;
private String company;
//getters and setters are also defined
}
Now Suppose That there is an entry in the table like this
Username: Chris
Password: Nolan
Company : Syncopy
If I populate my model with all these values then the resultList I obtain has 1 record.
However if only populate my model with username, ie Chris in this case, the resultlist I receive is always an empty ArrayList.
My Projects JPA Specification is 2.0, but if that is the issue, why does it return me the object in the first case and not the second.
Secondly , if that's the issue : is there any other way I can design a generic class that can take any model (of any type, with any number of fields populated)?
The last thing I want to do is to create a search method for all of my models.
Thanks and have a great day!

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