How to handle internationalization of names in Hiberante and db schema - java

I saw a few questions in stackoverflow, but they all refer to old answers (from 2004) and using hibernate xml mappings.
I am writing an API in Java, which will return products which are stored in the database based on an algorithm.
The API would also get a locale and decide to return the name in the locale language.
Product table:
product_id
name
price
How should I support internalization?
Should I add name_en name_fr etc columns?
Or should I create different tables?
How should I return the name in the required locale in Hibernate?
I am using annotations for O/R mapping

I would suggest adding a column "lang" that contains prefix of language (ex. en, fr, ro, etc.) and a column "name" that contains the name in all languages separated by a separator like "," and parse the string.

I would remove the name column, and create a one-to-many relationship with a separate table named localized_name:
create table localized_name (
product_key int not null,
locale nvarchar(32) not null,
name nvarchar(255) not null
)
My experience is with JPA, not Hibernate, but I'm guessing the code looks similar in both:
public class Product {
private Collection<LocalizedName> names;
}
In JPA I would add a #Size(min = 1) annotation to the field to ensure it contains at least one element when it is persisted.

Related

JOOQ multi-field custom type converter

We have some custom types that reflected to multiple db fields. For example
PersonName{
String salutation,
String firstName,
String lastName
}
stored as 3 separate db fields.
And it's boring to always write
db.select(PERSON.FIRST_NAME, PERSON.LAST_NAME, PERSON.SALUTATION, ... some other fields)
then fetch the record and create PersonName type from the appropriate record fields.
The idea is to define some multi-column custom field PERSON_NAME, which will be expanded by jooq into three "real" fields during the query execution, and packed to the one PersonName object in the result.
Looks like it's possible to do something like this with org.jooq.impl.AbstractField, but I'm wondering, may be there is a solution for such case already.
There are pending feature requests to support this kind of functionality:
https://github.com/jOOQ/jOOQ/issues/2360 (nested records)
https://github.com/jOOQ/jOOQ/issues/2530 (fetch groups)
With out-of-the-box functionality of jOOQ 3.6, you could store those columns somewhere as:
Field<?>[] personName = {
PERSON.SALUTATION,
PERSON.FIRST_NAME,
PERSON.LAST_NAME
};
And then select them as such:
db.select(personName)
.select(... some other fields);

History, Diff and reverts of persisted objects

In a Spring MVC / Spring Data project I need to implement a mechanism to track history, present differences and revert the changes to an entity object.
Let's say I have an entity with relationships with others like this:
#Entity
public Class ModelA{
#OneToOne(cascade = CascadeType.ALL)
private ModelB modelB;
#OneToOne(cascade = CascadeType.ALL)
private ModelC modelC;
}
I want to have the list of changes, the ability to compare and revert them. I know that using Ruby there are libs that provide this kind of functionality, but I'm not aware if such thing exist in Java.
Spring has a historiography API and Hibernate Envers had been incorporated in Core functionality, although I still can't find a simple example or some guidance how to implement it.
If it's relevant the used database is PostgreSQL and Oracle 11g, but I want to keep it database independent.
Use Enver and Auditions instead please.
One very interesting approach is given by Christian Bauer (Hibernate committer and author of Hibernate in Action and Java Persistence with Hibernate) in this post.
You create a HISTORY table:
create table ITEM (
ITEM_ID NUMBER(19) NOT NULL,
DESC VARCHAR(255) NOT NULL,
PRICE NUMBER(19,2) NOT NULL,
PRIMARY KEY(ITEM_ID)
)
create table ITEM_HISTORY (
ITEM_ID NUMBER(19) NOT NULL,
DESC VARCHAR(255) NOT NULL,
PRICE NUMBER(19,2) NOT NULL,
VERSION NUMBER(10) NOT NULL,
PRIMARY KEY(ITEM_ID, VERSION)
)
Then you map entities to a view instead:
create or replace view ITEM_VERSIONED (ITEM_ID, VERSION, DESC, PRICE) as
select I.ITEM_ID as ITEM_ID,
(select max(IH.VERSION)
from ITEM_HISTORY HI
where HI.ITEM_ID = I.ITEM_ID) as VERSION,
I.DESC as DESC,
I.PRICE as PRICE
from ITEM I
and the DML statements are resolved by INSTEAD OF TRIGGERS which are supported by PostgreSQL and Oracle:
create or replace trigger ITEM_INSERT
instead of insert on ITEM_VERSIONED begin
insert into ITEM(ITEM_ID, DESC, PRICE)
values (:n.ITEM_ID, :n.DESC, :n.PRICE);
insert into ITEM_HISTORY(ITEM_ID, DESC, PRICE, VERSION)
values (:n.ITEM_ID, :n.DESC, :n.PRICE, :n.VERSION);
end;
create or replace trigger ITEM_UPDATE
instead of update on ITEM_VERSIONED begin
update ITEM set
DESC = :n.DESC,
PRICE = :n.PRICE,
where
ITEM_ID = :n.ITEM_ID;
insert into ITEM_HISTORY(ITEM_ID, DESC, PRICE, VERSION)
values (:n.ITEM_ID, :n.DESC, :n.PRICE, :n.VERSION);
end;
This will work even for other applications that may not use Hibernate, yet they operate on the same DB.
If I understand well, what you ask is some sort of Memento pattern to manage some entities subject to history tracking.
In this case, my suggestion is to configure Spring Data to support a second database (i.e. a tracking database), where you will insert the history of the entities you are interested in.
Then you may create a new annotation (possibly using AspectJ) and apply it to your DAOs (e.g. to your repositories, if you are using them). This way, every time you make a CRUD operation on a tracked class (or, more precisely, on a dao/repository that manages a class you want to track), you make an "insert" in the tracking database storing the change that just occurred.
I can give you this reference, which does not match exactly your need, but may support you at finding the one solution that solves your issue.

Hibernate - reusable component definition in XML?

Is it possible to have reusable component definition in XML like pair #Embeddable and #Embedded ?
I've a complex component mapping and have to use it 10+ times throw XML mapping (yes, I know hot to copy mapping, but hope for a better solution). Unfortunately can't use annotations ...
Please be more specific as to what you trying to do?
If you are asking if the attributes of a component can be mapped to different columns then as per the documentation - and verified by you - you can do this:
#Embedded
#AttributeOverrides( {
#AttributeOverride(name="iso2", column = #Column(name="bornIso2") ),
#AttributeOverride(name="name", column = #Column(name="bornCountryName") )
} )
Country bornIn;
This will map the iso2 field in Country class to bornIso2 and name to bornCountryName. Hope this helps

Get physical column value with entity property value using hibernate

I have a table T with columns defined as usual.
#Entity
#Table(name="T")
public class T {
#Column(name="test_id")
private Long testId;
}
Given entity property "testId", I want to get corresponding DB column name (i.e. "test_id"). How could it be achieved?
Edit 1:
I want to keep this column at separate location with actual DB column name (test_id) than testId. I fetched these values from DB using HQL which have key as entity name (i.e. testId) and I want actual column name in DB.
If I understood your requirement correctly, you want to use HQL while having a consistent name for both DB column and the entity field, like this:
SELECT t.test_id FROM Test t
instead of
SELECT t.testId FROM Test t
There is only one way to do that - renaming the field to test_id. HQL works on entities, not on DB tables, so you must use proper field names in the query.
Since test_id contradicts the usual Java coding conventions, I would advise against it.
EDIT: Getting the annotation attribute value with reflection would work along this outline:
Field field = MyEntity.class.getDeclaredField("testId");
Column a = field.getAnnotation(Column.class);
String columnName = a.name();
I would try to avoid this by any means, but if you're really sure you'll need it, use:
Configuration configuration = sessionFactory.getConfiguration();
PersistentClass persistentClass = configuration
.getClassMapping(T.class.getName());
String columnName = ((Column) persistentClass.getProperty("testId")
.getColumnIterator().next()).getName();
See also Get table column names in Hibernate

Mapping db-imported countries to address entity with JPA

I ran some DDL script to setup a complete country table in my database. The country table's primary key column contains the corresponding ISO code for every country.
In my JPA project I have a User entity having an embedded Address entity and this Address entity has a reference to a Country. The relationship between User and Address seems to be no problem to me, but the relationship between Address and Country. I tried to map it as a ManyToOne relationship, since many addresses can share a country.
Problem is: I annotated the iso member variable of the Country class with Id -> Now, JPA/Hibernate complains about not having set the id of the country manually. But in this case, the id is already given and set, since I imported the data once and the ISO code is unique and by db schema means declared as primary key. In this special case, there is no need for updates or inserts in the country table - the information should be read only!
Any idea what to do, so I can use my countries table without altering?
Your question is missing some details, so the following involves a lot of guessing :-)
Your Country class should look something like:
#Entity
#Immutable
#Table(name="countries")
public class Country {
#Id
private String isoCode;
// all other attributes, getters / setters, etc...
}
#Immutable is a Hibernate extension to JPA standard; you don't have to put it on entity but having it will result in slightly better performance. Keep in mind that it will really make the Country immutable - you won't be able to create / update / delete countries through your application. You may want to configure cache for your Country entity as well if it's used often enough.
Your Address would have the following mapping to country:
#ManyToOne
#JoinColumn(name="country_iso_code")
private Country country;
Note the absence of "cascade" attributes - you don't need any. The final important point here is you actually need to get or load the Country instance to set it on address:
Country country = (Country) session.load(Country.class, isoCode);
// OR
Country country = (Country) session.get(Country.class, isoCode);
address.setCountry(country);
...
session.saveOrUpdate(address);
The first line above will not hit the database; use it if you know that country with such an ISO code exists. Second form will hit the database (or cache, if configured) and return NULL if country with that code does not exist.

Categories