I'm working on a JSF/JPA project with the Sakila Database on MySQL with Hibernate. When I want to merge a customer object into the DB, it takes 20 seconds. I assume it has to do with the mapping of my Entity Class but I cannot figure out what I have done wrong.
Here is my Customer class:
#Entity
#Table(name = "customer")
public class Customer implements Serializable, IEntity {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "customer_id")
private Short customerId;
#Basic(optional = false)
#Column(name = "first_name")
private String firstName;
#Basic(optional = false)
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Basic(optional = false)
#Column(name = "active")
private boolean active;
#Basic(optional = false)
#Column(name = "create_date")
#Temporal(TemporalType.TIMESTAMP)
private Date createDate;
#Basic(optional = false)
#Column(name = "last_update")
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdate;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "customerId")
private List<Rental> rentalList;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "customerId")
private List<Payment> paymentList;
#JoinColumn(name = "address_id", referencedColumnName = "address_id")
#ManyToOne(optional = false)
private Address addressId;
#JoinColumn(name = "store_id", referencedColumnName = "store_id")
#ManyToOne(optional = false)
private Store storeId;
//Constructor
//getter_setter
}
And in my CustomerService ejb I call the merge function:
#Named
#Stateless
public class CustomerService {
#PersistenceContext(name = "sakila")
EntityManager em;
public String merge(IEntity entity) {
try {
em.merge(entity);
return "success";
}catch(Exception e){
return "failure";
}
}
}
In the log I can see that hibernate executes 11037 select statements just like this one
select
paymentlis0_.customer_id as customer5_11_0_,
paymentlis0_.payment_id as payment_1_11_0_,
paymentlis0_.payment_id as payment_1_11_1_,
paymentlis0_.amount as amount2_11_1_,
paymentlis0_.customer_id as customer5_11_1_,
paymentlis0_.last_update as last_upd3_11_1_,
paymentlis0_.payment_date as payment_4_11_1_,
paymentlis0_.rental_id as rental_i6_11_1_,
paymentlis0_.staff_id as staff_id7_11_1_,
rental1_.rental_id as rental_i1_12_2_,
rental1_.customer_id as customer5_12_2_,
rental1_.inventory_id as inventor6_12_2_,
rental1_.last_update as last_upd2_12_2_,
rental1_.rental_date as rental_d3_12_2_,
rental1_.return_date as return_d4_12_2_,
rental1_.staff_id as staff_id7_12_2_,
customer2_.customer_id as customer1_5_3_,
customer2_.active as active2_5_3_,
customer2_.address_id as address_8_5_3_,
customer2_.create_date as create_d3_5_3_,
customer2_.email as email4_5_3_,
customer2_.first_name as first_na5_5_3_,
customer2_.last_name as last_nam6_5_3_,
customer2_.last_update as last_upd7_5_3_,
customer2_.store_id as store_id9_5_3_,
inventory3_.inventory_id as inventor1_9_4_,
inventory3_.film_id as film_id3_9_4_,
inventory3_.last_update as last_upd2_9_4_,
inventory3_.store_id as store_id4_9_4_,
staff4_.staff_id as staff_id1_13_5_,
staff4_.active as active2_13_5_,
staff4_.address_id as address10_13_5_,
staff4_.email as email3_13_5_,
staff4_.first_name as first_na4_13_5_,
staff4_.last_name as last_nam5_13_5_,
staff4_.last_update as last_upd6_13_5_,
staff4_.password as password7_13_5_,
staff4_.picture as picture8_13_5_,
staff4_.store_id as store_i11_13_5_,
staff4_.username as username9_13_5_,
staff5_.staff_id as staff_id1_13_6_,
staff5_.active as active2_13_6_,
staff5_.address_id as address10_13_6_,
staff5_.email as email3_13_6_,
staff5_.first_name as first_na4_13_6_,
staff5_.last_name as last_nam5_13_6_,
staff5_.last_update as last_upd6_13_6_,
staff5_.password as password7_13_6_,
staff5_.picture as picture8_13_6_,
staff5_.store_id as store_i11_13_6_,
staff5_.username as username9_13_6_,
address6_.address_id as address_1_1_7_,
address6_.address as address2_1_7_,
address6_.address2 as address3_1_7_,
address6_.city_id as city_id8_1_7_,
address6_.district as district4_1_7_,
address6_.last_update as last_upd5_1_7_,
address6_.phone as phone6_1_7_,
address6_.postal_code as postal_c7_1_7_,
store7_.store_id as store_id1_14_8_,
store7_.address_id as address_3_14_8_,
store7_.last_update as last_upd2_14_8_,
store7_.manager_staff_id as manager_4_14_8_,
store8_.store_id as store_id1_14_9_,
store8_.address_id as address_3_14_9_,
store8_.last_update as last_upd2_14_9_,
store8_.manager_staff_id as manager_4_14_9_
from
payment paymentlis0_
left outer join
rental rental1_
on paymentlis0_.rental_id=rental1_.rental_id
left outer join
customer customer2_
on rental1_.customer_id=customer2_.customer_id
left outer join
inventory inventory3_
on rental1_.inventory_id=inventory3_.inventory_id
left outer join
staff staff4_
on rental1_.staff_id=staff4_.staff_id
inner join
staff staff5_
on paymentlis0_.staff_id=staff5_.staff_id
inner join
address address6_
on staff5_.address_id=address6_.address_id
inner join
store store7_
on staff5_.store_id=store7_.store_id
left outer join
store store8_
on staff5_.staff_id=store8_.manager_staff_id
where
paymentlis0_.customer_id=?
It takes 20 seconds before Hibernate finally sends the update query
Information: Hibernate:
update
customer
set
active=?,
address_id=?,
create_date=?,
email=?,
first_name=?,
last_name=?,
last_update=?,
store_id=?
where
customer_id=?
I assume my mapping is wrong and customer is configured to be lazily loaded. Can someone point out my mistake?
Obviously in order to merge, JPA implementation (Hibernate here) needs to know if there are any changes made to detached (or lazy, not yet fetched) entities or not, thus it is checking its state with SELECT. Since you are cascadig (with CascadeType.ALL) your merge to all related entities - whole graph is refreshed.
Either do not cascade merges, or insteed merge use JPQL (or CriteriAPI) update if you need only to update parent entity disregarding it children.
Related
I have a problem using JPA.
I want to the fatherId and father coexistence,When I query with join table.
#Entity
public class Son {
#Id
#Column(name = "id")
private String id;
#Column(name = "father_id")
private String fatherId;
#OneToOne
#JoinColumn(name = "father_id")
private Father father;
}
You have to set one to read-only.
For example:
#Column(name = "father_id", insertable = false, updatable = false)
private String fatherId;
#OneToOne
#JoinColumn(name = "father_id")
private Father father;
Otherwise Hibernate has two ways to write the foreign key.
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
)
Currently I have the following 2 entities with a one to many relationship -
#Data
#Entity
#Table(name = "invoice_line")
#IdClass(InvoiceLinePK.class)
public class InvoiceLineEntity {
#Id
#Column(name = "line_id")
private String lineId;
#Id
#Column(name = "client_id")
private Integer clientId;
#Id
#Column(name = "invoice_id")
private String invoiceId;
#Column(name = "item_id")
private String itemId;
#Column(name = "amount")
private BigDecimal amount;
#ManyToOne
private InvoiceEntity invoice;
}
#Entity
#Table(name = "invoice")
#IdClass(InvoicePK.class)
#Data
public class InvoiceEntity {
#Id
#Column(name = "client_id")
private Integer clientId;
#Id
#Column(name = "invoice_id")
private String invoiceId;
#Column(name = "description")
private String description;
#Column(name = "txn_total_amount")
private BigDecimal txnTotalAmount;
#Column(name = "created_time", updatable = false)
#CreationTimestamp
private Date createdTime;
#Column(name = "updated_time")
#UpdateTimestamp
private Date updatedTime;
#OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "invoice")
private List<InvoiceLineEntity> invoiceLines;
}
In a case wherein let's say, one of my existing invoice has 3 lines and I receive a request that this particular invoice has been updated and it now has only 1 line instead of the previous 3 (so the other 2 have to be deleted), I would like to create a new Invoice object with this 1 InvoiceLineEntity and then do a invoiceRepository.save(invoice)
I am expecting that the other 2 InvoiceLine records would be automatically deleted because the orphanRemoval flag is enabled.
Can someone tell me how I can achieve this relationship by tweaking the entity relationship structure of the above 2 entities?
Your child entity must be the owner of the relationship, so that the orphans are allowed to be deleted
If you change and add mappedBy to that relation
#OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "bill")
private List<BillLine> billLines;
Then the BillLine must also hold a reference
public class BillLine {
#Id
#Column(name = "line_id")
private String lineId;
#Id
#Column(name = "company_id")
private Integer companyId;
#Id
#Column(name = "bill_id")
private String billId;
#Column(name = "item_id")
private String itemId;
#Column(name = "amount")
private BigDecimal amount;
#ManyToOne
private Bill bill;
}
Now it will remove the orphans
Also since you have multiple #Id on each entity. Do you know that you have to either declare a composite class or an embeddable class? Without one of those the multiple Ids are not valid.
Edit:
1) My bad mappedBy should be placed inside #OneToMany and not #JoinColumn. I have corrected it in my answer
2) Remove #JoinColumn. It is wrong in your configuration. By default #OneToMany inserts a column in the side of the #ManyToOne which holds the references to the primary table. You can override those default configurations and create a separate table for mappings but then you need the #JoinTable and I don't see any reason for that here.
This here
#JoinColumns(value = { #JoinColumn(name = "company_id", referencedColumnName = "company_id"),
#JoinColumn(name = "bill_id", referencedColumnName = "bill_id") })
definitely does not belong on #OneToMany
The following can be applied to #OneToMany but as said before I don't see any reason to do that and complicate a simple mapping which does not require a separate table.
#JoinTable(joinColumns = #JoinColumn(name = "company_id", referencedColumnName = "company_id"),
inverseJoinColumns = #JoinColumn(name = "bill_id", referencedColumnName = "bill_id") )
Check here for more information Jpa primary key
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;
}
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);