Why does hibernate join all table, declared this particular join? - java

I'm using hibernate-core:3.3.1.GA
I have three mappings:
class PlayerAccount:
#Entity
#Table(name = "player_account")
public class PlayerAccount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne(targetEntity = Player.class, fetch = FetchType.EAGER)
#JoinColumn(name="player_id")
private Player player;
//GET, SET
}
class PlayerAchievements:
#Entity
#Table(name="player_achievement")
public class PlayerAchievement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne(targetEntity = Player.class, fetch = FetchType.EAGER)
#JoinColumn(name = "player_id")
private Player player;
//GET, SET
}
and class Player
#Entity
#Table(name = "player")
#Inheritance(strategy = InheritanceType.JOINED)
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "ps_id")
private String psId;
#Column(name = "login")
private String login;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
//GET, SET
}
Issue:
When I try to write a simple criteria query like the following:
Criteria criteria = getSession().createCriteria(PlayerAccount.class);
criteria.add(Restrictions.eq("playerId", playerId));
criteria.list();
The resulting sql-query contains joins have the following view:
SELECT
-- columns to select
FROM player_account this_
LEFT OUTER JOIN player player2_
ON this_.player_id=player2_.id
LEFT OUTER JOIN external_partner_player player2_1_
ON player2_.id=player2_1_.player_id
LEFT OUTER JOIN player_achievement achievemen3_
ON player2_.id=achievemen3_.player_id
WHERE player_id = 2362189
The thing is player_achievements may contain more than one row with the same player_id value. Since we probably have a duplicated result in that criteria query. How to fix that using hibernate whithout the writing an sql-query?

Because the Fetch Types are eager. That means that you always want the entities loaded when you load the main entity.
You can use FetchType.LAZY for best perform. Here explains better: https://howtoprogramwithjava.com/hibernate-eager-vs-lazy-fetch-type/

You need to do SELECT DISTINCT, like this:
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

Related

Update object in relation by Hibernate ORM in Java

I have question about access to data.
I have that DB:
[country: id, country_name],
[city: id, country_id, city_name],
[address: id, shop_data_id, city_id, address_data],
[shop_data: id, data]
My relations country-city one to many, city-address one to many, address-shop_data one to one.
I'm looking for information that can I do that SQL query with ORM, or what is the best way do do it in ORM.
UPDATE shop_data
INNER JOIN country ON country.id=1
INNER JOIN city ON country.id=city.country_id
INNER JOIN address ON city.id= address.city_id
INNER JOIN shop_data ON address.shop_data_id=shop_data.id
SET shop_data.data="shop data string"
WHERE shop_data.id=address.shop_data_id
I know that in SQL I should start by shop_data, but by doing this I want to show that I want start in ORM by country entity.
I wrote entities in Hibernate with annotation
#Entity
#Table(name="country")
public class Country{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "country")
private String country;
#OneToMany(mappedBy = "country", fetch=FetchType.LAZY)
#JsonBackReference
private List<City> cities = new ArrayList<>();
// getters/setters ..
}
#Entity
#Table(name="city")
public class City{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "city")
private String city;
#OneToMany(mappedBy = "city", fetch=FetchType.LAZY)
private List<Address> adresses = new ArrayList<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="country_id")
#JsonIgnore
#JsonManagedReference
private Country country;
// getters/setters ..
}
#Entity
#Table(name="address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "address")
private String address;
#Column(name = "district")
private String district;
#Column(name = "post_code")
private String postCode;
#OneToOne
#JoinColumn(name="shop_data_id")
private ShopData shopData;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="city_id")
private City city;
// getters/setters ..
}
#Entity
#Table(name="shop_data")
public class shopData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "data")
private String data;
// getters/setters ..
}
I know that I can use getters starting from country that returns List<ObjectType> and from there get my object. Next run function update and update right row. But in this way are done some number of queries.
Is it possible to do by Java Hiberante ORM by one query? Or which way is the best to minimize query amount? By this method I also want to update next also address data.
You should definitely read a book about JPA/Hibernate to understand what JPQL or HQL supports. You can just do joins as you do them with SQL, except for DML statement, but you don't need that. In your case a simple subquery is enough to model what you need. A possible query could look like the following:
UPDATE ShopData s
SET s.data="shop data string"
WHERE EXISTS (
SELECT 1
FROM Country c
WHERE c.id = 1 AND c.city.address.shopData.id = s.id
)

Join two tables with hibernate

I am looking to create a DAO which represents a join of two tables with Java Hibernate. Here is the SQL I'd like to represent (Postgres 9.6 incase that matters):
SELECT tableOneValue, tableTwoValue
FROM table_one, table_two
WHERE table_one_filter = 2 AND table_one_id = table_two_id;
These tables have a OneToOne relationship.
Table1.java
#Entity
#Data
#Table(name="table_one")
public class TableOneDao implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "table_one_id")
private int tableOneId;
#Column(name = "table_one_value")
private String tableOneValue;
#Column(name = "table_one_filter")
private int tableOneFilter;
}
Table2.java
#Entity
#Data
#Table(name="table_two")
public class TableTwoDao implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "table_twp_id")
private int tableTwpId;
#Column(name = "table_two_value")
private String tableTwoValue;
}
I'm very new to hibernate so maybe this isn't the right way to think with it. What I would love to do is define a SomeDao class where I can do: daoManager.findAll(SomeDao.class, Pair.of("tableOneFilter", 2));
This would return a List<SomeDao> where we get all the rows that satisfy tableOneFilter == 2.
You need to use the #OneToOne and #JoinColumn annotation.
Pay special attention to the userDetail attribute mapping.
For example, the user class:
#Entity
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_ID")
private long id;
#Column(name = "USERNAME", nullable = false, unique = true)
private String username;
#Column(name = "PASSWORD")
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="USR_DET_ID")
private UserDetail userDetail;
// Add Constructor, Setter and Getter methods
}
And this user details class:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USR_DET_ID")
private long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL")
private String email;
#Column(name = "DBO")
private LocalDate dob;
// Add Constructor, Setter and Getter methods
}
Check the full code here.
Here is a JPA query which will work with your existing entity structure with the latest version of hibernate.
SELECT t1.tableOneValue, t2.tableTwoValue
FROM TableOneDao AS t1 JOIN TableTwoDao AS t2 ON t1.table_one_id = t2.table_two_id
WHERE t1.table_one_filter = ?
You can write a JPQL statement which is much better. Here is the sample solution:
SELECT NEW com.test.package.dao(t1.valueOne, t2.valueTwo)
FROM table_one t1 JOIN table_two t2
WHERE t1.filter = 2 AND t1.id = t2.id;
Please refer to this link and jump to the section where it mentions Result Classes (Constructor Expressions). Hope it helps. Thanks.

Hibernate - multi-language tables

I'm trying to build a multi-language database, so I've used this database design as a approach for mine.
Now I've two problems/questions:
I want to retrieve all LocalizedEvent for a given language and given categoryId. How can I make a inner join over the LocalizedCategory table with Hibernate Criteria API?
With SQL I would make this statement to get all LocalizedEvent + LocalizedCategory:
SELECT * FROM event e
INNER JOIN
localized_event le ON (le.event_id = e.event_id)
INNER JOIN
localized_category lc ON (lc.category_id = e.category_id)
WHERE
le.locale = 'de' AND lc.locale = 'de'
My current approach looks like this without getting the LocalizedCategory (with Criteria API):
Criteria c = session.createCriteria(LocalizedEvent.class, "localizedEvent");
c.createAlias("localizedEvent.event", "event");
c.createAlias("event.category", "category");
c.add(Restrictions.eq("category.categoryId", categoryId));
c.add(Restrictions.eq("localizedEvent.locale", language));
I think my mapping is not 100% correct. The entity LocalizedEvent should have a property localizedCategory, but I don't want to save the ID of this localizedCategory (therefore I'm using the #Transient annotation) in the LocalizedEvent table, e.g. using a ManyToOne relation (joining LOC_CATEGORY_ID). But I think it's not possible to do this, isn't it? I would have to map this transient field to LocalizedEvent "manually", because Hibernate is not supporting this mapping (if I'm right).
(Using JDBC this property/mapping would cause no problems, because I can easily make my inner joins and assign the property localizedCategory to the LocalizedEvent in a RowMapper or so).
My entities looks like this:
Event
#Entity
#Table(name = "EVENT")
public class Event {
#Id
#GeneratedValue
#Column(name = "EVENT_ID", unique = true)
private Long eventId;
#Column(name = "DATE")
private Date date;
#OneToMany(mappedBy = "event")
private Set<LocalizedEvent> localizedEvents;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
}
LocalizedEvent
#Entity
#Table(name = "LOCALIZED_EVENT")
public class LocalizedEvent {
#Id
#GeneratedValue
#Column(name = "LOC_EVENT_ID")
private Long locEventId;
#ManyToOne
#JoinColumn(name = "EVENT_ID")
private Event event;
#Transient
private LocalizedCategory localizedCategory;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "LOCALE")
private String locale;
}
Category
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID", unique = true)
private Long categoryId;
#OneToMany(mappedBy = "category")
private Set<LocalizedCategory> localizedCategories;
#OneToMany(mappedBy = "category")
private Set<Event> events;
}
LocalizedCategory
#Entity
#Table(name = "LOCALIZED_CATEGORY")
public class LocalizedCategory {
#Id
#GeneratedValue
#Column(name = "LOC_CATEGORY_ID")
private Long locCategoryId;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
#Column(name = "NAME")
private String name;
#Column(name = "LOCALE")
private String locale;
}

SQL Query Too Complex To Express In JPA Criteria API?

I have an SQL query that gets me exactly the data I need. The problem is that we are trying to express all queries in JPA Criteria API to maintain portability, and I can't figure out how to map this particular query.
The problem is that the JPA Criteria API Subquery class lacks the multiselect() method that CriteriaQuery class has. As you can see in the SQL query, I have computed fields in the sub-query which don't exist in the entity. Thus, I have no way to retrieve these fields.
I would be quite appreciative if anyone knows a solution or could offer guidance, or even if someone could validate that what I am trying to achieve in JPA Criteria API is not possible.
The SQL:
SELECT w.NAME AS 'wave_name',
Count(*) AS 'num_lines',
Sum(qty_ordered) AS 'num_units',
Count(DISTINCT unit_of_work_id) AS 'num_units_of_work',
Sum(completed_units) AS 'completed_units',
( Sum(completed_units) + Sum(qty_scratched) ) / Sum(qty_ordered) AS 'perc_completed_units'
FROM (SELECT t.id,
t.wave_id,
t.quantity_requested AS 'qty_ordered',
t.quantity_scratched AS 'qty_scratched',
t.unit_of_work_id AS 'unit_of_work_id',
Ifnull(m.quantity, 0) AS 'qty_picked',
CASE
WHEN Ifnull(m.quantity, 0) > quantity_requested THEN
quantity_requested
ELSE Ifnull(m.quantity, 0)
END AS 'completed_units'
FROM task t
LEFT OUTER JOIN (SELECT move.task_id,
Sum(quantity) AS 'quantity'
FROM move
GROUP BY task_id) m
ON m.task_id = t.id) s
JOIN wave w
ON w.id = s.wave_id
GROUP BY w.name;
The entities:
#Entity
#Table(name = "task")
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "wave_id", nullable = false)
private Wave wave;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "unit_of_work_id", nullable = false)
private UnitOfWork unitOfWork;
#OneToMany (cascade = CascadeType.ALL, mappedBy = "task")
private Set<Move> moves = new HashSet<Move>();
#Column (name = "quantity_requested")
private Long quantityRequested;
#Column (name = "quantity_scratched")
private Long quantityScratched;
}
#Entity
#Table(name = "wave")
public class Wave {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "wave", cascade = CascadeType.ALL)
private Set<Task> tasks = new HashSet<Task>();
}
#Entity
#Table(name = "unit_of_work")
public class UnitOfWork {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
#Column (name = "id")
private Long id;
#OneToMany(mappedBy = "unitOfWork", cascade = CascadeType.ALL)
private Set<Task> tasks = new HashSet<Task>();
}
#Entity
#Table(name = "move")
public class Move {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private Long id;
#ManyToOne (cascade = CascadeType.ALL)
#JoinColumn (name = "task_id", nullable = false)
private Task task;
#Column (name = "quantity")
private Long quantity;
}
I would say use named parameters or native query approach for this.
For example:
Named parameters:
public interface Repo extends JpaRepository<AEntity, String> {
#Query("select a from AEntity a where a.BEntity.name = :name")
public aMethod( #Param("name") String name)
}
OR
Native query approach:
public interface Repo extends JpaRepository<AEntity, String> {
#Query(value = "select * from Tablename t where t.name = :name", nativeQuery=true)
public aMethod(#Param("name") String name)
}
Check this link if you are using spring jpa
http://docs.spring.io/spring-data/data-jpa/docs/1.4.x/reference/htmlsingle/#jpa.named-parameters

Why does Hibernate put inner join in the case of one-to-one relationship?

I have two entities:
UnsubscribedPartner for unsubscribed from mailing partners
#Entity
#Table(schema = "mailing", name = "unsubscribed_partner")
public class UnsubscribedPartner {
#Id
#Column(name = "partner_id")
private int partnerId;
#Column(name = "unsubscription_date")
private Date date;
#OneToOne(targetEntity = Partner.class, fetch = FetchType.EAGER)
#JoinColumn(name = "partner_id")
private Partner partner;
//GET, SET
}
Partner partner's class
#Entity
#Table(schema = "partner", name = "partner")
public class Partner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "email")
private String email;
#OneToOne(fetch = FetchType.EAGER, mappedBy = "partner")
private UnsubscribedPartner unsubscribedPartner;
//GET, SET
}
I constructed the following criteria query:
String email;
//...
Criteria criteria = getSession().createCriteria(Partner.class);
if(!(email == null)){
criteria.add(Restrictions.eq("email", email));
}
Criteria unsubscribedCrieria = criteria.createCriteria("unsubscribedPartner", "unsbcr");
unsubscribedCrieria.add(Restrictions.isNull("unsbcr.reason"));
But the result SQL query is
select
count(*) as y0_
from
partner.partner this_
inner join
mailing.unsubscribed_partner unsbcr1_
on this_.id=unsbcr1_.partner_id
where
unsbcr1_.unsubscription_reason_id is null
Inner join is not appropriate here, because the unsubscribed_partner tables may not any partner from the partner table, therefore I need LEFT OUTER JOIN instead. How can I fix that?
The documentation states that createCriteria(String, String) is functionally equivalent to createCriteria(String, String, int) using CriteriaSpecification.INNER_JOIN for the joinType.
So, try with createCriteria("unsubscribedPartner", "unsbcr", CriteriaSpecification.LEFT_JOIN) instead.

Categories

Resources