Apply i18n to a Hibernate backed Spring Web API - java

I have a couple of Hibernate entities stored in a DB that I want to internationalize.
For example my "country" entity:
#Table(name = "country")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "name")
private String name;
}
What I now want to do is enhance the API that handles the retrieval of the countries to return the country entry in the appropriate language. (e.g. adding ?lang=en to the query)
For that I have a country controller:
#RestController
#Api(tags = "Country")
public class CountryController {
private final CountryDao countryDao;
public CountryController(CountryDao countryDao) {
this.countryDao = countryDao;
}
#ApiOperation(value = "View a list of available countries.")
#GetMapping(path = "/entity/countries")
public Iterable<Country> getCountries() {
return countryDao.findAll();
}
}
How would I do that?
I have thought of adding a new i18n table that holds message keys with the available translations.
But in the end I still would like the API to return a simple JSON entry containing only the country name that the user is interested in, without having to attach all available translations.
I tried with AttributeConverter to mark the fields on the entity with #Convert(converter = I18nConverter.class)and search the i18n table for the matching key, but with that approach I don't have access to the specified language in the query...
public class I18nConverter implements AttributeConverter<String, String> {
private final I18nEntryDao i18nEntryDao;
#Override
public String convertToDatabaseColumn(String attribute) {
...
}
#Override
public String convertToEntityAttribute(String dbData) {
...
}
}

Related

How can I exclude properties when serializing an object to JSON with jackson?

I have a class implementing Serializable, which is mapped to a database table. It looks like this:
#Entity
#Table(name = "users")
public class Users implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
#Column(name = "name", nullable = false)
public String name;
#Column(name = "email", nullable = false)
public String email;
#Column(name = "status", nullable = false)
public String status;
}
For the most part, I want all these properties to be included in the JSON. However, there is a specific case where I want to exclude status, but I can't figure out a good way of doing this with Jackson.
My controller looks something like this:
public class UserController {
private final ObjectMapper mapper;
#Inject
public UserController(ObjectMapper mapper) {
this.mapper = mapper;
}
public CompletionStage<JsonNode> getUserList() {
// Get list of Users and return the JSON; with all User properties included
}
public CompletionStage<JsonNode> getUser(Long userId) {
// Get a single user from JPA, in a promise
return userDatabase.get(userId).thenApply(user -> { // user is type User
// Here, I don't want to include "status" in the JSON.
return mapper.valueToTree(user);
});
}
}
So when I do mapper.valueToTree(user), of course, it includes all properties of User, but I want to exclude status in this specific route/function while keeping it included in all other places its serialized.
I know I can use #JsonIgnore to ignore it always, but can I do this just sometimes?
Some solutions I thought of are:
filter through the properties and get rid of status
Copy user over to an ObjectNode and manually remove status
Neither of these seems ideal though, I feel like there has to be a cleaner approach with Jackson.

How to map results of a Hibernate Query to a DTO object?

I have the following 3 Hibernate Entities within my Java Project:
CompanyStatus
#Entity(name = "company_status")
#Table(name = "company_status")
public class CompanyStatus implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#JsonProperty
#Column(name = "company_status_id")
private Integer companyStatusId;
#JsonProperty
#Column(name = "company_status_label")
private String companyStatusLabel;
}
Employee Status
#Entity(name = "employee_status")
#Table(name = "employee_status")
public class EmployeeStatus implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
#Column(name = "employee_status_id")
private Integer employeeStatusId;
#JsonProperty
#Column(name = "employee_status_name")
private String employeeStatusName;
// many other fields
}
CompanyStatusEmployeeStatus (Entity linking the 2 entities- one to one relationship)
#Entity(name = "company_status_employee_status")
#Table(name = "company_status_employee_status")
public class CompanyStatusEmployeeStatus implements Serializable {
// int(20)
#Id
#JsonProperty
#Column(name = "company_status_id")
private Integer companyStatusId;
// int(20)
#JsonProperty
#Column(name = "employee_status_id")
private Integer employeeStatusId;
}
I only want to return the necessary fields in my JSON response to the front end , so In order to do so I have created a smaller CompanyStatusDTO object that also has an EmployeeStatusDTO list nested
CompanyStatusDTO
public class CompanyStatusDTO {
#JsonProperty
private Integer companyStatusId;
#JsonProperty
private String companyStatusLabel;
#JsonProperty
private List <EmployeeStatusDTO> employeeStatusDTOs;
}
EmployeeStatusDTO
public class EmployeeStatusDTO {
#JsonProperty
private Integer employeeStatusId;
#JsonProperty
private String employeeStatusName;
}
However, I am relatively new to using Hibernate - is there a way that I can create a query that will map results directly from my MySQL DB to my CompanyStatusDTOobject?
If so, how can do I this?
you can directly map query result to you desired DTO using NativeQuery (datatype must match)
String q = "select ... from table"; // your sql query
Query query = getEntityManager().createNativeQuery(q, "EmployeeStatusDTO");
EmployeeStatusDTO data = (EmployeeStatusDTO) query.getSingleResult();
return data;
This is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
If you adapt the CompanyStatus and CompanyStatusEmployeeStatus entities a bit and add the following:
public class CompanyStatus implements Serializable {
//...
#OneToMany(mappedBy = "companyStatus")
private Set<CompanyStatusEmployeeStatus> employeeStatuses;
}
public class CompanyStatusEmployeeStatus implements Serializable {
//...
#JsonProperty
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "company_status_id", insertable = false, updatable = false)
private CompanyStatus companyStatus;
#JsonProperty
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "employee_status_id", insertable = false, updatable = false)
private EmployeeStatus employeeStatus;
}
Your model could look like the following:
#EntityView(CompanyStatus.class)
public interface CompanyStatusDTO {
#IdMapping
Integer getCompanyStatusId();
String getCompanyStatusLabel();
#Mapping("employeeStatuses.employeeStatus")
List<EmployeeStatusDTO> getEmployeeStatusDTOs();
}
#EntityView(EmployeeStatus.class)
public interface EmployeeStatusDTO {
#IdMapping
Integer getEmployeeStatusId();
String getEmployeeStatusName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CompanyStatusDTO c = entityViewManager.find(entityManager, CompanyStatusDTO.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You can try this:
public class Dao{
private SessionFactory sessionFactory;
public Dao(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public <T> T save(final T o){
return (T) sessionFactory.getCurrentSession().save(o);
}
public void delete(final Object object){
sessionFactory.getCurrentSession().delete(object);
}
public <T> T get(final Class<T> type, final Long id){
return (T) sessionFactory.getCurrentSession().get(type, id);
}
public <T> List<T> getAll(final Class<T> type) {
final Session session = sessionFactory.getCurrentSession();
final Criteria crit = session.createCriteria(type);
return crit.list();
}
// and so on, you should get the idea
and you can then access like so in the service layer:
private Dao dao;
#Transactional(readOnly = true)
public List<MyEntity> getAll() {
return dao.getAll(MyEntity.class);
}

Unable to get results of controller whilst using Join Annotations in hibernate

I am new to hibernate and trying to implement join annotations of hibernate, but facing this weird issue. As I have attached pojos when I run my controller no output is seen but a new row got populated in user_song_rel table as a foreign key to the song table.
In addition, value of that entry is null(in MySQL screenshot) despite there are entries in the song table corresponding to song_ids.
Please find attached schema of both tables.
Thanks in advance :)
Schema Structure
User Song Mapping
#NamedQueries({
#NamedQuery(
name="findUserSongByUserID",
query="from UserSongRel usr where usr.user_id = :user_id"
)
})
#Entity
#Table(name="user_song_rel")
public class UserSongRel {
public UserSongRel() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int usr_id;
private String user_id;
private String song_id;
private String song_src;
private int song_state;
#Temporal(TemporalType.TIMESTAMP)
private Date add_date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "song_id")
private SongInfo songInfo;
Song
#Entity (name = "song_info")
public class SongInfo {
#Id
private String song_id;
private String s_name;
private String artist;
private String album;
private int duration;
private Date rel_date;
private int popularity;
#OneToMany(mappedBy = "songInfo")
private List<UserSongRel> userSongRelList=new ArrayList<UserSongRel>();
Controller
#RequestMapping(value = "/getUserSongs/{uid}", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<?> getUserSongs(#PathVariable String uid,
HttpServletResponse response, HttpServletRequest request){
SongInfo usr= songService.getUserSongs(uid);
return ResponseEntity.ok().body(usr);
}
DAO
public SongInfo getUserSongs(String uid) {
Session s = sf.getCurrentSession();
UserSongRel songs=s.get(UserSongRel.class, 1);
List<UserSongRel> songList =new ArrayList<UserSongRel>();
songList.add(songs);
return songList.get(0).getSongInfo();
}
Select query after running controller.

Why user defined #javax.persistence.Converter isn't recognized by hibernate when mapping the entity field to database column

I have the following Entity containing a field of Enum type:
#Entity
#Table(name = "INPUT_DATA")
public class InputDataEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "INPUT_DATA_SEQ", allocationSize = 1, sequenceName = "INPUT_DATA_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "INPUT_DATA_SEQ")
private Long id;
#Column(name = "FIELD1", nullable = false)
private String field1;
#Column(name = "FIELD2", nullable = false)
#Convert(converter = Type.Converter.class)
private Type field2;
// getters and setters
}
The Enum type looks like:
public enum Type {
ENUM_ITEM_1("item1"),
// more items
ENUM_ITEM_N("itemN");
private String code;
private Type(String code) {
this.code = code;
}
public static Type fromString(String name) {
switch (name) {
case "item1":
return ENUM_ITEM_1;
// more cases
case "itemN":
return ENUM_ITEM_N;
default:
throw new IllegalArgumentException("Wrong value for Type");
}
}
#Override
public String toString() {
return code;
}
#javax.persistence.Converter
public static class Converter implements AttributeConverter<Type, String> {
#Override
public String convertToDatabaseColumn(Type attribute) {
return attribute.toString();
}
#Override
public Type convertToEntityAttribute(String s) {
return Type.fromString(s);
}
}
}
The problem is that hibernate doesn't recognize my Converter when I want to fetch data from the database.
I've also tried:
#Embedded and #Embeddable but with no luck.
#Enumerated(EnumType.STRING) but again with no luck.
My question is:
how to make hibernate to recognize my converter when converting the appropriate field?
Many thanks in advance.
I eventually ended up by implementing a StringValuedEnum interface and its relevant reflector and type class by implementing EnhancedUserType, ParameterizedType as it was described here.
This helped me to properly store into and retrieve from DB data corresponding to user defined enum types, although the questions with converters remains still open. If someday a proper answer will be given, that will be very appreciated.

Hibernate criteria: search by content on a property list in an entity

I want to search by content on a property in an entity
I have a simple class to define a User:
#Entity
public class User {
#Id
#Column(name = "pers_id")
private int persId;
#Column(name = "full_name")
private String fullName;
#OneToMany
#JoinColumn(name = "PERS_ID")
private List<UserLanguages> languages = new ArrayList<UserLanguages>();
}
A User can have multiple languages, here is the class to make the link between user and a language.
#Entity
public class UserLanguages {
#Column(name="pers_id")
private int persId;
#Id
#Column(name="lang_iso_code")
private String langISO;
#Column(name="lang_full_name")
private String langFullName;
#Column(name="order_seq")
private int order;
}
#Entity
public class Language {
#Id
#Column(name="ID")
private long id;
#Column(name = "CODE")
private String code;
}
I have created a object to do search:
public class UserFilter {
private String name;
private List<Language> languages;
}
I have defined a service:
#Service("userService")
public class UserServiceImpl implements UserService {
#Override
public List<User> findByFilter(UserFilter userFilter) {
final Criteria criteria = userDao.createCriteria();
if (userFilter.getName() != null) {
for (final String token : userFilter.getName().toLowerCase().trim().split(" ")) {
criteria.add(Restrictions.like("fullName", "%" + token + "%"));
}
}
if (null != userFilter.getLanguages() && userFilter.getLanguages().size() > 0) {
final List<String> contents = new ArrayList<String>(userFilter.getLanguages().size());
for (final Language lang : userFilter.getLanguages()) {
contents.add(lang.getCode());
}
criteria.add(Restrictions.in("languages", contents));
}
return userDao.findByCriteria(criteria);
}
My question is how can I do search on languages. I want to find all users with this or thoses languages defined in the userFilter param.
The part about languages doesn't work in the method findByFilter in the service. Can you help me?
First of all, the UserLanguages entity should be named UserLanguage : it represents one language, and not several.
Then, the pers_id column is a foreign key to the User entity. It should thus be mapped as a ManyToOne relationship to the User entity rather than a basic column.
Finally, and to answer your question (I'll assume you want to find the users having at least one user language whose langISO code is in the contents list) : you should use a join :
// inner join between User and UserLanguages
criteria.createAlias("languages", "userLanguage");
// restriction on the langISO property of UserLanguage
criteria.add(Restrictions.in("userLanguage.langIso", contents));

Categories