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.
Related
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.
I have following classes:
Company.class:
public class Company {
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
#Column(name = "score")
private BigDecimal score;
}
and Employee.class
public class Employee {
#ManyToMany(fetch = FetchType.EAGER, mappedBy="employees")
private Set<Company> companies;
}
The Score column of Company is always null in the db and never updated via dao, because there is other table containing score for each unique pair Company-Employee.
I need the value of Score, only for the case when I fetch Employee by id, so this case all Company instances in the Set should contain score, thus I will get Employee-Company score pairs where employee is fetched Employee.
I have following code to achieve that:
public Employee get(Long id) {
Employee emp = (Employee) dao.find(id);
List<Company> compList = compnanyService.getByEmpId(id);
Set<Company> compSet = new HashSet<Company>(compList);
emp.setCompanies(compSet);
return emp;
}
And Company Dao contains method:
public List<Company> getByEmpId(Long id) {
final Query query = this.entityManager.createNativeQuery("select company.comp_id, ...some other fields, score.score from company join score on company.company_id=score.company_id where score.employee_id=:employee_id",
Company.class);
query.setParameter("employee_id", id);
List<Company> comps = query.getResultList();
return comps;
}
The problem is that getByEmpId(id) gives a ResultList where company.score is null though executed in the db it is not null.
I suspected that there is some caching intervening, so I tried to remove some columns from the native query, and it should have invoked an exception with "no column found" (or alike) message while mapping, but this method still gives List<Company> with all fields on their places though Hibernate prints out my native query in the console with all changes I make.
What am I doing wrong here and how to achieve what I need? Thank you.
It might be associated with first level cache, which can be out of sync when using native SQL queries. From here:
If you bypass JPA and execute DML directly on the database, either
through native SQL queries, JDBC, or JPQL UPDATE or DELETE queries,
then the database can be out of synch with the 1st level cache. If you
had accessed objects before executing the DML, they will have the old
state and not include the changes. Depending on what you are doing
this may be ok, otherwise you may want to refresh the affected objects
from the database.
So you can try using refresh method from EntityManager.
So I ended up doing that:
Created view in db from the query:
CREATE VIEW companyscore AS select company.comp_id, score.emp_id ...some other fields, score.score from company join score on company.comp_id=score.comp_id;
Created corresponding entity CompanyScore with composite primary id as comp_id and emp_id and created view as table.
Changed Employee entity to:
public class Employee {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "emp_id")
private Set<CompanyScore> companies;
}
This way I not only have score field always consistent, but I can choose set of fields to show as the whole Company class is quite extensive and I don't need all the fields for this particular case.
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
I have the following:
#Entity
#NamedQuery(name = "listCarsBySecurity", query = "SELECT c FROM Car c WHERE c.security = :security"
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
// Some more fields
#NotNull()
#OneToOne (fetch = FetchType.LAZY, orphanRemoval=true)
private Security security = new Security();
// Some getters and setters
As you can see, the Car class has a "Security" object which is LAZY fetched. The security class looks like:
#Entity
public class Security {
#Id #GeneratedValue
private Long id;
// Security equipment. Add in alphanumerical order
private boolean abs;
private boolean airbag;
private boolean antispin;
// Some getters and setters
as you can see, the named query list try to list all cars which has a security entity equal to a provided security object.
The persistence method looks like:
#Stateless
public class CarEJB {
#PersistenceContext(unitName = "carcmsPU")
private EntityManager em;
public List<Car> listCarsBySecurity(Security security) {
TypedQuery<Car> query = em.createNamedQuery("listCarsBySecurity", Car.class);
query.setParameter("security", security);
return query.getResultList();
}
And a junit test looks like:
#Test
public void searchCar() throws Exception {
// Looks up the EJBs
carEJB = (CarEJB) ctx.lookup("java:global/classes/CarEJB");
// Create a new Ferrari with security = ABS brakes and Airbag
Car car = new Car();
car.setMake("Ferrari");
car.setModel("Some model");
car.setSubModel("Some sub model");
car.setEngine("Some engine");
car.setYear(1999);
car.getFuel().setGasoline(true);
car.setGearbox(Gearbox.AUTOMATIC);
car.setKilometres(323);
car.setDescription("This is a description");
Security security = new Security();
security.setAbs(true);
security.setAirbag(true);
car.setSecurity(security);
carEJB.createCar(car); // Persist
// Create a new security object and match it to the former one
Security securityObject = new Security();
securityObject.setAbs(true);
securityObject.setAirbag(true);
List<Car> carList = carEJB.listCarsBySecurity(securityObject);
assertTrue("Should contain at least 1 car with ABS and Airbag", carList.size() > 0 );
for (Car carTemporary : carList) {
System.out.println(carTemporary.toString());
}
}
The thing is that the list does not contain any cars at all. And I think I know why; the named query does try to match the security_id with NULL (since I have not define it).
My question is: How can I perform a query by passing a object as a query parameter with no ID and by not specify all fields which shall be compared inside that object? (or how exclude the ID from a search)?
Best regards
You can define a named query using OR and passing each one of the object's attributes. You can also use Criteria API to build a query based on the fields you want to query about. Since you already have a named query I'll leave that one to you.
If you decide to go that way (tough field by field comparation is kind of insane if your entity has way too many attributes). Using criteria you can do something like this:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> queryRoot = query.from(Car.class);
query.select(queryRoot);
Path<String> pathToYourField = root.get(yourField); //yourField is a variable containing the field.
//You can store all the variables in a list, iterate
//over them and do this for each one.
query.where(builder.and(builder.equal(pathToYourField, "particularValue"))); //You compare the path against a value.
//Rest of the fields / paths
TypedQuery<Car> typedQuery = entityManager.createQuery(query);
List<Car> cars = typedQuery.getResultList();
EDIT: About performance, check this links:
JPA Criteria vs NamedQueries
Another answer regarding Criteria vs HQL
Criteria overhead discussion
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