I want to get international content from database based on locale provided in hibernate query. This is a question about hibernate mapping but please feel free to propose better database design if mine is wrong.
My DB design (simplified):
db design
So I have table with non translatable data and additional table with translated content but with additional field "locale" for distinction of language.
My java classes looks like this:
public class Car {
private Long id;
private Long length;
private Long weight;
private CarTranslated carTranslated;
// getters and setters
public class CarTranslated {
private Long id;
private Long carId;
private String desc;
// getters and setters
I want to be able to get one car with single query. With regular jdbc I would use something like this sql query:
public Car getById(Long id, Locale locale) {
Car c = new Car();
String sql = "select c.car_id, c.length, c.weight, ct.id, ct.descryption,
ct.car_id as "Translated car_id" from car c join car_translated ct on
(c.car_id = ct.car_id) where c.car_id ="+ id+" and ct.locale ='"+locale+"'";
// code to set fields of the object using ResultSet
return c;
}
What would be a hibernate annotation mapping and query for this setup? I tried several attempts but to no avail. Currently my best attempt was as below:
Mapping:
#Entity
#Table(name="CAR")
public class Car {
#Id
#Column(name="car_id")
private Long carId;
#Column (name="weight")
private Long carWeight;
#Column (name="length")
private Long carLength;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name ="CAR_ID")
private CarTranslated localized;
// getters and setters
#Entity
#Table(name="CAR_TRANSLATED")
public class CarTranslated {
#Id
#Column (name="id")
private Long id;
#Column (name="car_id")
private Long carId;
#Column (name="descryption")
private String desc;
#Column(name="locale")
private Locale locale;
DAO:
public Car getCarById(Locale locale, Long id) {
Car car = new Car();
try {
Session session = HibernateUtils.getSessionFactory().openSession();
Criteria cr = session.createCriteria(Car.class)
.add(Restrictions.eq("carId", id));
Criteria cr1 = session.createCriteria(CarTranslated.class)
.add(Restrictions.eq("locale", locale));
car = (Car) cr.uniqueResult();
car.setLocalized((CarTranslated) cr1.uniqueResult());
} catch (Exception e) {
System.out.println(e.getMessage());
}
return car;
}
This is a work-around and I'm wondering what would be a proper way to do this?
You should have an annotation on both columns when mapping to a FK. (JavaDoc)
Related
#Entity
#Table(name = "customers")
public class Customer implements Serializable{
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private int custID;
private String custName;
#Id
private String email;
private int phone;
#OneToMany (mappedBy = "customer", fetch = FetchType.LAZY)
private List<Transaction> transaction;
#Entity
#Table(name = "transactions")
public class Transaction implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int transID;
private Date date;
private int amount;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "custID", nullable= false)
private Customer customer;
These are my entities, and I have a method:
#PostMapping("/record-transaction")
public Transaction recordTransaction(#RequestBody Transaction transaction) {
return transactionService.addTransaction(transaction);
}
But when I try to create JSON in postman, I get into a loop where while entering values for transaction, at the end I must enter the Customer object as well and when I am entering customer object at the end I again reach to enter the transaction's values. Its like a never ending loop. Help
I couldn't think of anything to do at all. My mind enters the loop itself.
Decouple your DB entities from your request/response by using an intermediate DTO.
Controller:
#PostMapping("/record-transaction")
public TransactionResponse recordTransaction(#RequestBody TransactionRequest body) {
return TransactionResponse.from(transactionService.addTransaction(
body.getDate();
body.getAmount();
body.getCustomerId();
));
}
TransactionRequest:
public class TransactionRequest {
//don't need ID here it'll be auto generated in entity
private Date date;
private int amount;
private int customerId;
}
TransactionResponse:
public class TransactionResponse {
private int id;
private Date date;
private int amount;
private int customerId;
public static TransactionResponse from(Transaction entity) {
return //build response from entity here
}
}
TransactionService:
//when your entity is lean may as well pass the values directly to reduce boilerplate, otherwise use a DTO
public Transaction addTransaction(Date date, int amount, int customerId) {
Customer customerRepo = customerRepo.findById(customerId).orElseThrow(
() -> new CustomerNotFoundException();
);
Transaction trans = new Transaction();
trans.setDate(date);
trans.setAmount(amount);
trans.setCustomer(customer);
return transactionRepository.save(trans);
}
If you want to embed the customer model inside TransactionResponse or TransactionRequest it'll be fairly easy to do and this solution will produce way nicer contract and swagger docs than a bunch of use case specific annotations in your entity.
In general decoupling you request/response payloads, service dtos and entities from each other results in code with more boilerplate but easier to maintain and without weird unexpected side effects and specific logic.
#Entity
public class ClassA {
some attributes
#Enumerated(value = EnumType.STRING)
private EnumObject status;
}
My Enum:
public enum EnumObject {
OK,
BAD,
SOME_CASE,
ANOTHER_CASE;
There are a possibility to say never return Entity when status=BAD for all queries
Kindly see if the below notions help you in achieving what you are after:
2.3.21. #Where
Sometimes, you want to filter out entities or collections using custom
SQL criteria. This can be achieved using the #Where annotation, which
can be applied to entities and collections.
Example 78. #Where mapping usage
public enum AccountType {
DEBIT,
CREDIT
}
#Entity(name = "Client")
public static class Client {
#Id
private Long id;
private String name;
#Where( clause = "account_type = 'DEBIT'")
#OneToMany(mappedBy = "client")
private List<Account> debitAccounts = new ArrayList<>( );
#Where( clause = "account_type = 'CREDIT'")
#OneToMany(mappedBy = "client")
private List<Account> creditAccounts = new ArrayList<>( );
//Getters and setters omitted for brevity
}
https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-column-where
2.3.23. #Filter
The #Filter annotation is another way to filter out entities or
collections using custom SQL criteria. Unlike the #Where annotation,
#Filter allows you to parameterize the filter clause at runtime.
Now, considering we have the following Account entity:
Example 85. #Filter mapping entity-level usage
#Entity(name = "Account")
#FilterDef(
name="activeAccount",
parameters = #ParamDef(
name="active",
type="boolean"
)
)
#Filter(
name="activeAccount",
condition="active_status = :active"
)
public static class Account {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
private Client client;
#Column(name = "account_type")
#Enumerated(EnumType.STRING)
private AccountType type;
private Double amount;
private Double rate;
#Column(name = "active_status")
private boolean active;
//Getters and setters omitted for brevity
}
https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-column-filter
Also take a look at Global hibernate filter on all database queries which uses AspectJ to intercept the queries if you want to do it in another way.
I am using Spring-Boot with JPA and a MySQL backend. Now I got quite confused about the repositories Spring-Boot provides. I know these are quite powerful (and seem to be quite useful since they can shorten your code a lot). Still, I do not understand how to represent Joins within them, since the result-set should be a combination of specified attributes in the select of a few Entities.
Now let's assume we have three tables Book, Author, AuthorOfBook, where the last one is simply connecting Book and Author by a combined Primary key. I guess we had the following Java-Classes:
Entity Book:
#Entity
#Table(name="BOOK")
public class Book {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "TITLE")
private String title;
}
Entity Author
#Entity
#Table(name="AUTHOR")
public class Author {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "LASTNAME")
private String lastname;
#Column(name = "FIRSTNAME")
private String firstname;
//Let's assume some getters and setters and a constructor
}
Entity AuthorOfBook:
#Entity
#Table(name="BOOK")
public class Book {
#EmbeddedId
private AuthorOfBookId pk;
}
An Embedded ID
#Embeddable
public class AuthorOfBookId implements Serializable {
private int authorId;
private int bookId;
}
Repository
#Repository
public interface AuthorOfBookRepository extends JpaRepository<,AuthorOfBookId> {
}
Now how would I represent that query:
SELECT b.name, a.firstname, a.lastname from AuthorOfBook ab inner join Book b on b.id = ab.book_id inner join Author a on a.id = ab.author_id where a.lastname = :lastname;
in my repository? I know the signature would need to be like
#Query([the query string from above])
public (...) findAuthorAndBookByAuthorLastname(#Param("lastname") String lastname);
but I cannot make out what Type the return would be like. What is that method returning? (simply AuthorOfBook would not work I guess)
You don't want AuthorOfBook as a separate Entity. Book should have a field of type Author as a #ManyToOne relationship. That way, given any Book, you can find the author's details.
If you want to handle audits fields you can do something like this:
Audit class
#Embeddable
public class Audit {
#Column(name = "created_on")
private Timestamp createdOn;
#Column(name = "updated_on")
private Timestamp updatedOn;
#Column(name = "is_deleted")
private Boolean isDeleted;
//getters and setters
}
AuditListener to update automatically audits fields
public class AuditListener {
private Long loggedUser = 1001L;
/**
* Method to set the fields createdOn, and isDeleted when an entity is persisted
* #param auditable
*/
#PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if (audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setIsDeleted(Boolean.FALSE);
audit.setCreatedOn(Timestamp.from(Instant.now()));
}
/**
* Method to set the fields updatedOn and updatedBy when an entity is updated
* #param auditable
*/
#PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(Timestamp.from(Instant.now()));
}
}
And add this to the entities
#EntityListeners(AuditListener.class)
public class Book implements Auditable {
#Embedded
private Audit audit;
I would like to find an entity using a critera with restriction on the value of an attribute of a second entity wich is a member of the embedded id of my first entity.
First entity :
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Second entity :
#Entity
public class Owner {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
#OneToMany (mappedBy = "id.owner")
private List<Car> cars;
// getters and setters...
}
In this example, I would like to obtain the car with the color 'black', model 'batmobile' and the owner's firstname 'Bruce' (oops... spoiler ;) )
I tried to do something like that but it won't work :
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("id.owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();
Result :
Hibernate: select this_.model as model1_0_0_, this_.owner_id as owner_id3_0_0_, this_.color as color2_0_0_ from Car this_ where this_.color=? and this_.model=? and o1_.firstname=?
ERROR: Unknown column 'o1_.firstname' in 'where clause'
What is the right way to obtain what I want ?
update
I tried in hql :
String hql = "FROM Car as car where car.color = :color and car.id.model = :model and car.id.owner.firstname = :firstname";
Query query = em.createQuery(hql);
query.setParameter("color", "black");
query.setParameter("model", "batmobile");
query.setParameter("firstname", "Bruce");
List<Car> cars = query.getResultList();
It works but is there a way to do this with criteria ?
You forgot to add the #Column annotation on top of the firstname and lastname fields (and the color field in Car). In hibernate if a field is not annotated, it doesn't recognize it as a database field. This page should give you a good idea about how to set up your model objects.
NOTE: You can have the column annotation over the getters and be fine, but you didn't show the getters. Either place is fine.
Look at what HQL is spitting back out, specifically the statement (formated for easier reading):
select
this_.model as model1_0_0_,
this_.owner_id as owner_id3_0_0_,
this_.color as color2_0_0_
from Car this_
where
this_.color=?
and this_.model=?
and o1_.firstname=?
It looks like hibernate is translating the field "id.owner" to "o" as your alias told it to to, but for some reason it's not writing down that "id.owner=o" as intended. You may want to do some research into why it may be doing that.
As per https://hibernate.atlassian.net/browse/HHH-4591 there is a workaround.
You have to copy the needed relation-property of the #EmbeddedId (owner in this case) to the main entity (Car in this case) with insertable = false, updatable = false as follows
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#ManyToOne
#JoinColumn(name = "column_name", insertable = false, updatable = false)
private Owner owner;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Then just create directly the alias instead of using the composite id property
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();
I have 2 entities as
PayoutHeader.java
#Entity
public class PayoutHeader extends GenericDomain implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column
private Integer month;
#Column
private Integer year;
#OneToOne
private Bank bank;
#Column
private Double tdsPercentage;
#Temporal(javax.persistence.TemporalType.DATE)
private Date **chequeIssuedDate**;
#Temporal(javax.persistence.TemporalType.DATE)
private Date entryDate;
}
PayoutDetails .java
#Entity
public class PayoutDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private PayoutHeader payoutHeader;
#Column
private Double amount;
#Column
private String bankName;
#Temporal(javax.persistence.TemporalType.DATE)
private Date clearingDate;
#OneToOne
private Advisor advisor;
#Column
private Long **advisorId**;
}
I want to write query using Hibernate Criteria like
Select pd.* from PayoutDetails pd, PayoutHeader ph where pd.payoutheaderId = ph.id and pd.advisorId = 1 and and ph.chequeIssuedDate BETWEEN STR_TO_DATE('01-01-2011', '%d-%m-%Y') AND STR_TO_DATE('31-12-2011', '%d-%m-%Y') ";
I have written query like this
public List<PayoutDetails> getPayoutDetails(AdvisorReportForm advisorReportForm) {
Criteria criteria = getSession().createCriteria(PayoutDetails.class);
if (advisorReportForm.getAdvisorId() != null && advisorReportForm.getAdvisorId() > 0) {
criteria.add(Restrictions.eq("advisorId", advisorReportForm.getAdvisorId().toString()));
}
criteria.setFetchMode("PayoutHeader", FetchMode.JOIN)
.add(Restrictions.between("chequeIssuedDate", advisorReportForm.getFromDate(), advisorReportForm.getToDate()));
return criteria.list();
}
But is giving error as
org.hibernate.QueryException: could not resolve property:
chequeIssuedDate of: org.commission.domain.payout.PayoutDetails
I think is is trying to find chequeIssuedDate field in PayoutDetails, but this field is in PayoutHeader. How to specify alias during join ?
The criteria.setFetchMode("PayoutHeader", FetchMode.JOIN) just specifies that you want to get the header by a join, and in this case is probably unneeded. It doesn't change which table is used in the restrictions. For that, you probably want to create an additional criteria or an alias, more or less as follows:
public List<PayoutDetails> getPayoutDetails(AdvisorReportForm advisorReportForm) {
Criteria criteria = getSession().createCriteria(PayoutDetails.class);
if (advisorReportForm.getAdvisorId() != null && advisorReportForm.getAdvisorId() > 0) {
criteria.add(Restrictions.eq("advisorId", advisorReportForm.getAdvisorId().toString()));
}
criteria.createCriteria("payoutHeader")
.add(Restrictions.between("chequeIssuedDate", advisorReportForm.getFromDate(), advisorReportForm.getToDate()));
return criteria.list();
}
or (using an alias)
public List<PayoutDetails> getPayoutDetails(AdvisorReportForm advisorReportForm) {
Criteria criteria = getSession().createCriteria(PayoutDetails.class);
if (advisorReportForm.getAdvisorId() != null && advisorReportForm.getAdvisorId() > 0) {
criteria.add(Restrictions.eq("advisorId", advisorReportForm.getAdvisorId().toString()));
}
criteria.createAlias("payoutHeader", "header")
.add(Restrictions.between("header.chequeIssuedDate", advisorReportForm.getFromDate(), advisorReportForm.getToDate()));
return criteria.list();
}
See the Hibernate docs on Criteria Queries for examples of this.
It's also likely not appropriate to convert the advisorId to a string, as it is in fact a Long and probably mapped to a number field in sql.
It's common to also not map something like this advisorId field at all if you map the advisor, and use a restriction based on the advisor field, similarly to the way this deals with the payoutHeader field.
I wouldn't worry about getting all the fields from the header, but it may behave a bit differently if you get the createCriteria version to work.