java hibernate override enum/string mapping values - java

I've got a following Hibernate model:
#Entity
#Table(name = "category")
public class Category {
#Enumerated(EnumType.STRING)
#Column(name = "type")
private CategoryType type;
This is the enumeration referenced by hibernate:
public enum CategoryType {
INCOME, OUTCOME;
}
THe corresponding database field is a varchar which takes 2 possible values: "CategoryIncome" and "CategoryOutcome".
This method actually calls hibernate:
public List<Category> findAllByType(CategoryType type) {
session = sessionFactory.openSession();
tx = session.beginTransaction();
Query query = session.createQuery(
"FROM Category WHERE type = :type");
query.setParameter("type", type);
List list = query.list();
tx.commit();
session.close();
return list;
}
I managed to get my code work (I mean it compiles), but it works badly - it executes following SQL query:
WHERE type = "INCOME"
whereas I would like it to be:
WHERE type = "CategoryIncome"
How can I map enum values into string values for hibernate? I know that EnumType.STRING tells hibernate to cast the enum values to string (could be EnumType.ORDINAL to cast it to integers). But how can I override the default enum-string mapping?

You will have to use your customized usertype for Hibernate persistance, Hibernate uses name() function of enum to get string representation, not toString().
See this
Hibernate #Enumerated mapping

Related

ListJoin and multiselect with criteriaquery with Polymorphism

I was wondering if anyone knows the cause of the Exception being thrown? I have the following entities below. Is it because some Employees returned aren't DriverEmployees and thus do not have routes?
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
}
#Entity
public class DriverEmployee extends Employee {
#OneToMany(fetch = FetchType.LAZY)
private List<Routes> routes;
}
#Entity
public class Routes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
private String description;
}
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
// Want routes to be returned as a list in the multiselect.
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
query.getResultList().forEach((t) -> {
process(t.get("name", String.class));
processList(t.get("routes", List.class).size());
});
The error I'm receiving is below. Anyone know how I can get the following to run successfully?
Caused by: java.lang.IllegalStateException: No data type for node: org.hibernate.hql.internal.ast.tree.IdentNode
+-[IDENT] IdentNode: 'routes' {originalText=routes}
In this current example you have tuple which contains one object of DriverEmployee and one object of Routes (I recommend you to rename this entity to Route and set #Table(name = "routes"). According to JavaDoc of multiselect():
If the type of the criteria query is CriteriaQuery (i.e., a criteria query object created by either the createTupleQuery method or by passing a Tuple class argument to the createQuery method), a Tuple object corresponding to the arguments of the multiselect method, in the specified order, will be instantiated and returned for each row that results from the query execution.
So it means that you CAN'T make a Tuple like Tuple<DriverEmployee, List<Routes>>
The way how you can reach such behavior of your current query is to do it by yourself. For instance:
your method with criteria:
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
List<Tuple> resultList = query.getResultList();
Map<String, List<Routes>> resultMap = getMapFromResultList(resultList);
resultMap.entrySet().forEach((name, routesList) -> {
process(name);
processList(routesList);
});
and the method of obtaining the map:
private Map<String, List<Routes>> getMapFromResultList(List<Tuple> tuples) {
Map<String, List<Routes>> map = new HashMap<>();
tuples.forEach(tuple -> {
String name = tuple.get("name", String.class);
Routes routes = tuple.get("routes", Routes.class);
map.computeIfPresent(name, (key, value) -> {
value.add(routes);
return value;
});
map.computeIfAbsent(name, (key) -> {
List<Routes> routesList = new ArrayList<>();
routesList.add(routes);
return routesList;
});
});
return map;
}
I guess the Hibernate JPA Criteria implememtation doesn't support that. If you really want to use the JPA Criteria API to do this, you are probably out of luck. In JPQL/HQL you could model this like SELECT e.name, r FROM DriverEmployee e LEFT JOIN e.routes r. On top of that you have to extract the values like Andrew Kolesnyk mentioned.
However, this is is the perfect use case for Blaze-Persistence Entity Views.
Blaze-Persitence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(DriverEmployee.class)
interface EmployeeDto {
#IdMapping
Integer getId();
String getName();
List<RoutesDto> getRoutes();
}
#EntityView(Routes.class)
interface RoutesDto {
#IdMapping
Integer getId();
String getName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
EmployeeDto dto = entityViewManager.find(entityManager, EmployeeDto.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 and it can also be saved back. Here a sample repository
#Repository
interface EmployeeRepository {
EmployeeDto findOne(Long id);
}
It will only fetch the mappings that you tell it to fetch.
Here you can see an example project: https://github.com/Blazebit/blaze-persistence/tree/master/examples/spring-data-webmvc

Select only some columns from a table

Is there a way to select only some columns from a table using jpa?
My tables are huge and I am not allowed to map all the columns in my entities. I tried to create an entity (as a side note, I don't have PKs in my tables):
#Entity
#Table(name = "SuperCat")
#Getter
#Setter
public class Cat{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name="nameCat")
private String name;
}
and then in my repository to
public interface CatRepository extends
CrudRepository<Cat, Long> {
#Query(
"SELECT name FROM Cat")
Page<Cat> getAlCats(Pageable pageable);
This is only a simple example, but the idea is the same. I have searched a lot and I found projections, but there you need to map the whole table, then I found native queries, but still doesn't apply. I know I can return an Object and the other solution is to use query with NEW and create my own object (no #entity, like a pojo). But is there a way that I can do this using jpa, to be able to use repository and services, if I am creating my own pojo then i will create a #transactional class put the queries (with NEW) there and this is it. I don't like this approach and I don't think that the jpa does't allow you to select only some columns, but I didn't find a proper way.
Maybe you will ask what is the result if I am doing like this:
I get this error: "Cannot create TypedQuery for query with more than one return using requested result type [java.lang.Long]"
(For new queries, I am talking about : http://www.java2s.com/Tutorials/Java/JPA/4800__JPA_Query_new_Object.htm maybe I was not clear)
You can do the same by using below approach.
Just create a constructor in entity class with all the required parameters and then in jpa query use new operator in query like below.
String query = "SELECT NEW com.dt.es.CustomObject(p.uniquePID) FROM PatientRegistration AS p";
TypedQuery<CustomObject> typedQuery = entityManager().createQuery(query , CustomObject.class);
List<CustomObject> results = typedQuery.getResultList();
return results;
And CustomObject class should look like below with the constructor.
public class CustomObject {
private String uniquePID;
public CustomObject(String uniquePID) {
super();
this.uniquePID = uniquePID;
}
public String getUniquePID() {
return uniquePID;
}
public void setUniquePID(String uniquePID) {
this.uniquePID = uniquePID;
}
}
spring-data-jpa projection not need to map the whole table, just select the necessary fileds :
// define the dto interface
public interface CatDto {
String getName();
// other necessary fields
...
}
#Query(value = "select c.name as name, ... from Cat as c ...)
Page<CatDto> getAllCats(Pageable pageable);
By this way, CatDto is an interface and it only includes some fileds part of the whole table. Its fields name need to match the select field's alias name.

org.hibernate.PropertyAccessException: could not get a field value by reflection getter of

I looked around for answers on this and it looks like it's something to do with reflections but since I haven't dealt with them I'm not sure how to proceed. I'm trying to build a HQL query that returns only rows from a table that match a column that is a FK to a PK in another table.
Here's what my code looks like:
Entity Book:
#Entity
#Table(name="book")
public class Book {
#Id
private String bookNumber;
private String bookName;
private String bookDescription;
private double bookPrice;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="shelfnumber")
Shelf shelf;
Entity Shelf:
#Entity
#Table(name="shelf")
public class Shelf {
#Id
private String shelfNumber;
#OneToMany(mappedBy="shelf",cascade=CascadeType.ALL)
Set<Book> shelfBooks;
Note: both entity classes have getters/setters
ActionClass:
DynaActionForm searchForm =((DynaActionForm)form);
String shelfNum = searchForm.getString("shelfNum");
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
try{
session.getEntityMode();
Query query = session.createQuery("FROM Book B WHERE B.shelf = :shelfNum"); //here is where the error happens: B.shelf is the problem but don't know why
query.setParameter("shelfNum", shelfNum);
List books = query.list();
request.setAttribute("books", books);
} finally{
session.close();
}
Error message I'm getting:
org.hibernate.PropertyAccessException: could not get a field value by reflection getter of com.library.entity.Shelf.shelfNumber
and further down I see:
Caused by: java.lang.IllegalArgumentException: Can not set java.lang.String field com.library.entity.Shelf.shelfNumber to java.lang.String
Thank you for any help!
Your query compares an instance of Shelf with an instance of String. That can't work. A Shelf is a Shelf. It's not a String.
The query should be
select b from Book b where b.shelf.shelfNumber = :shelfNum
Also, please tell me you don't actually recreate a SessionFactory at each and every request!
And please stop using raw types. We're not in 2003 anymore.

How to fetch a field lazily with Hibernate Criteria

Each row of the table Person (having name, firstname and age) shall be read.
EntityManager em = emf.createEntityManager();
Session s = (Session) em.getDelegate();
Criteria criteria = s.createCriteria(Person.class);
criteria.setFetchMode("age", FetchMode.SELECT);
But the SQL shows
Hibernate:
select
person0_.name,
person0_.firstname,
person0_.age
from
SCOPE.PERSON person0_
How to let the age be lazy ONLY for the Criteria??
I think that lazy mode only makes sense with associations. If you are accessing a plain table it will load all the fields.
If you want the age field not to appear in the SQL and so not being loaded into memory then use projections:
Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);
Setting the FetchMode of the "age" property on a criteria has no effect because the fetching strategy at this point is for associated objects only but not for properties. See section 20.1. Fetching strategies of the hibernate docs.
Hibernate uses a fetching strategy to retrieve associated objects
if the application needs to navigate the association. Fetch strategies
can be declared in the O/R mapping metadata, or over-ridden by a
particular HQL or Criteria query.
The only way for lazy loading of a property is the #Basic annotation set to FetchType.LAZY. See here, or if you use .hbm.xml files for mapping use lazy=true, see this section of the hibernate docs.
The #Basic annotation allows you to declare the fetching strategy for
a property. If set to LAZY, specifies that this property should be
fetched lazily when the instance variable is first accessed. It
requires build-time bytecode instrumentation, if your classes are not
instrumented, property level lazy loading is silently ignored.
Lazy loading of properties also use buildtime bytecode instumentation (hibernate is changing the entity classes after compilation to allow lazy loading of properties). Read 20.1.8. Using lazy property fetching
An other possible solution (except for all the other solutions) to your problem is to make a simpler Person class and use a constructor query like:
public class PersonDTO {
private String name;
private String firstname;
private Person(String name, String firstname) {
this.name = name;
this.firstname = firstname;
}
// getters & setters
}
Query q = session.createQuery("select new your.package.name.PersonDTO("
+ "p.name, p.firstname) from Person p");
q.list();
You could even use your existing Person class, just extend it with an appropriate constructor, but I would prefer explicitness.
But all the solutions presented here do not implement a lazy loading of the age attribute. The only way to do this is the #Basicannotation, or you have to implement your own lazy loading.
If your age is an object like the PersonAge of #Dragan you could associate the fecth mode with the criteria rather than the entity like you do.
So, I think you have three options:
age as primitive and projection like #Paco says (Person.age will be null and not a Proxy, you lose the lazyness that you want)
age as primitive without projection (more bytes in the wire)
age as PersonAge + criteria.setFetchMode (you will get the lazyness that you want at the cost of an extra object/table/mapping)
For Projection you could use ResultTransformer to
Criteria crit = session.createCriteria(Person.class);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.property("name"));
projList.add(Projections.property("firstname"));
crit.setProjection(projList);
crit.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
String name = (Long) tuple[0];
String firstName = (String) tuple[1];
return new Person(name , firstName);
}
#Override
public List<Reference> transformList(List collection) {
return collection;
}
});
I think you could create a PersonProxy on your own that triggers a query for retrieve the age but this is kind of awful.
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
String name = (Long) tuple[0];
String firstName = (String) tuple[1];
return new PersonProxy(name , firstName);
}
class PersonProxy {
Person realPerson;
public getAge(){
// create a query with realPerson.id for retrieve the age.
}
}
Your reasoning is valid (in general; we can however argue about the specific example of the age field), but unfortunately there is no straight-forward solution for this. Actually, Hibernate has the concept of fetch profiles, but it is currently very limited (you can override the default fetch plan/strategy only with the join-style fetch profiles).
So, the possible workaround to your issue could be as follows.
1) Move age to a separate entity and associate the Person entity with it with a lazy one-to-one relationship:
#Entity
class PersonAge {
private Integer age;
}
#Entity
class Person {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true, optional = false)
#JoinColumn(name = "PERSON_AGE_ID")
private PersonAge personAge;
public Integer getAge() {
return personAge.getAge();
}
public void setAge(Integer age) {
personAge.setAge(age);
}
}
2) Define a fetch profile which overrides the default one:
#FetchProfile(name = "person-with-age", fetchOverrides = {
#FetchProfile.FetchOverride(entity = Person.class, association = "personAge", mode = FetchMode.JOIN)
})
3) Enable this profile for each session in the application:
session.enableFetchProfile("person-with-age");
Depending on the framework you use, there should be an easy hook/interceptor which you will use to enable the profile for each session (transaction) that is craeted. For example, an approach in Spring could be to override AbstractPlatformTransactionManager.doBegin of the transaction manager in use.
This way the personAge will be eagerly loaded in all the sessions in the application, unless the fetch profile is explicitly disabled.
4) Disable the fetch profile in the session in which you use the desired Criteria query:
session.disableFetchProfile("person-with-age");
This way the default fetch plan/strategy is used (specified in the entity mappings), which is the lazy loading of the PersonAge.
You can simply define a new entity SimplePerson mapped to the same persons database table which contains only the following attributes:
id
name
firstName
This way, when selecting a SimplePerson with both Criteria and HQL, the age column will not be retrieved.
Another alternative is to use lazy loading for basic attributes, but mapping multiple subentities to the same database table is much more flexible.
I would like to add (or maybe clarify) the followings. Given the main class (Settlement) with an attribute class (Customer):
#Entity
#Table(name = "settlement")
public class Settlement extends IdBasedObject {
...
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "customer_id_fk")
private Customer customer;
}
#Entity
#Table(name = "customer", schema = SchemaUtil.SCHEMA_COMMON)
public class Customer extends IdBasedObject {
#Column(name = "organization_type")
#Enumerated(EnumType.ORDINAL)
private CompanyType companyType;
#Column(name = "organization_legal_name")
private String companyLegalName;
...
}
If you would like to get all the distinct customers from the Settlement, you would use the Projections distinct on the 'customer' property and followed by creating an alias from the Settlement class:
public List<Customer> findUniqueCustomer() throws Exception {
Session session = super.getSessionFactory().openSession();
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.distinct(Projections.property("customer")));
Criteria criteria = session.createCriteria(Settlement.class);
criteria.setProjection(projectionList);
criteria.createAlias("customer", "customer");
return criteria.list();
}
Now, if you do that, you will get back a list of non-proxy error 'could not initialize proxy - no Session' for each of the customer object.
Fortunately, criteria provides the setResultTransformer function that will 're-shape' the return.
criteria.setResultTransformer(new ResultTransformer() {
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Customer customerObject = (Customer) tuple[0];
Customer customer = new Customer();
customer.setId(customerObject.getId());
customer.setVersion(customerObject.getVersion());
customer.setCompanyType(customerObject.getCompanyType());
customer.setCompanyLegalName(customerObject.getCompanyLegalName());
return customer;
...
}
#SuppressWarnings("rawtypes")
#Override
public List<Customer> transformList(List collection) {
return collection;
}
});
The tuple[0] essentially contains the customer object value, since the customer object is not proxied, you will get the error. In the transformTuple function, you have a chance to 're-create' each of the customer object thereby avoiding the 'non-proxied' error.
Please give a try.

Hibernate dynamic instantiations with collections, is it possible?

I would like to write a hql query using a dynamic instantiation with a list as one of it's parameters.
Simplified example:
A HQL query with a dynamic instantiation:
select new x.y.UserDto(u.name, u.contacts) from User u where u.xyz=:param1 ...
and my dto class constructor is:
public class UserDto {
private String name;
private List contacts;
public UserDto(String name, List contacts) {
this.name = name;
this.contacts = contacts;
}
...
}
And the entity mapping:
public class User {
#olumn(name="NAME")
String name;
#ManyToMany(targetEntity= Contacts.class, fetch = FetchType.EAGER)
#JoinTable(name="USER_DEPARTMENT_CONTACTS",
joinColumns=#JoinColumn(name="DEPARTMENT_ID"),
inverseJoinColumns=#JoinColumn(name="USER_ID"))
private List<Contacts> contacts;
...
}
So as you can see all I want is to create a new object that has some properties and collections of an entity.
I can understand that Hibernate would need one or more queries to achieve this as this would generate multiple result rows for each entity.
Does anyone knows if it is possible to create a new object which is a combination of properties and collections?
Sorry but it is not possible. According to JPA specification,
The type of the query result specified by
the SELECT clause of a query is AN
ENTITY abstract schema type, A
STATE-FIELD type - NOT A COLLECTION -,
the result of an aggregate function,
the result of a construction
operation, OR SOME SEQUENCE OF THESE.
You could use the following instead:
select DISTINCT new x.y.UserDto(u) from User u LEFT JOIN FETCH u.contacts
So, this way you would have the users with your contacts fetched

Categories