Hibernate Lazy Loading makes criteria slow to run - java

I am experiencing a problem with hibernate and lazy loading of objects.
basically I want to load an class which has an eagerly loaded field and not load the lazy fields of child classes
Take the following QuestionVO class
#Entity
#Table(name = "question")
public class QuestionVO extends BaseDAOVO implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5867047752936216092L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "questionText", unique = false, nullable = false, length = 4000)
#Size(min = 3, max = 4000)
#Pattern(regexp = MobileAppsRegexConstants.GENERAL_ALLOWED_CHARCHTERS, message = "Question Text Not valid.")
private String questionText;
#ManyToOne(fetch = FetchType.EAGER)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name = "MENU_STYLE_ID", nullable = true)
private MenuStyleVO menuStyle;
}
Take the following MenuStyleVO class
#Entity
#Table(name = "menu_style")
public class MenuStyleVO extends BaseDAOVO implements Serializable{
/**
*
*/
private static final long serialVersionUID = 3697798179195096156L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "menuStyleName", unique = false, nullable = false, length = 200)
private String menuStyleName;
#Column(name = "menuTemplate", unique = false, nullable = false, length = 200)
private String menuTemplate;
#OneToOne(fetch = FetchType.LAZY, optional=false)
#Cascade({ CascadeType.SAVE_UPDATE })
#JoinColumn(name="logo_id")
#JsonProperty("logo")
private ApplicationImageVO logo;
}
And this ApplicationImageVO class
#Entity
#Table(name = "application_image")
public class ApplicationImageVO extends BaseDAOVO implements Serializable {
/**
*
*/
private static final long serialVersionUID = -9158898930601867545L;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image1242x2208")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage1242x2208;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image1536x2048")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage1536x2048;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "image2048x1536")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private SubmissionLauncherImagesVO launcherImage2048x1536;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "logo")
#Cascade({ CascadeType.ALL })
#JsonIgnore
private MenuStyleVO menuStyleLogo;
}
If L load the QuestionVO class from the database using the following hibernate criteria code - all the lazy fields of MenuStyleVO and ApplicationImageVO are also loaded.
On complicated use cases, this results in this query getting very slow
public QuestionVO findMasterAppQuestionById(int id) {
Criteria criteria = currentSession().createCriteria(QuestionVO.class);
criteria.add(Restrictions.eq("id", id));
QuestionVO questionVO = (QuestionVO) criteria.uniqueResult();
return questionVO;
}
What I am wondering is - would it be possible to load the QuestionVO class and its eager fields and tell hibernate to ignore lazy fields from the other classes bar those that are needed?
Cheers
Damien

Last time we faced an issue like this we used a constructor on parent class, which use only the desired fields of determined query.
I can't remember in fully how constructor inside a jpql query works, but it must be something like this:
select new com.package.class(c.field1, c.field2) from com.package.class c
Remember, a constructor with same arguments must be present on the desired entity.
Pros:
- Better query perfomance;
- Can be replicated with other arguments;
Cons:
- Pretty limited, you can only use this hack on the main entity you are querying;
- Includes a constructor only for determined query, poor design;
Also, you should take a look on EnttyGraphs of JPA. Seems quite promising, but didn't work as desired in our project.
Btw, Hibernate has put us many times on performance issues, hope this hack help you, good luck!
Edit:
Why this pattern would help in performance issues?
Basically, with the example i've showed before, you are not loading everything via Hibernate, only the two fields (field1 and field2) of the main entity. Without using a constructor you shoudn't be able to do that, because your query would not result in a collection of the desired entity, but in a collection of two objects each iteration (Object[]). Using the constructor pattern you are creating instances of the desired entity, but only selecting a few fields from database, and that's why this pattern can help you, you are returning a collection of the desired entity with only a few fields.

Related

Circumventing Attribute Limitations of Many-to-Many Relations in JPA

I am using PostgreSQL 12.11, JPA 3.1.0, and Hibernate 5.6.10. This might become important because I am doing things that apparently do not work with JPA 2.0.
My goal is to add an attribute to a many-to-many relationship. I found this posting. #Mikko Maunu states that "There is no concept of having additional persistent attribute in relation in JPA (2.0)." To me, this sounds like what I want to do is not possible. However, the answer is rather old and might not be complete anymore.
Beside the time gap and the version gap, this is, in my opinion, a new question because I am doing something that is probably questionable and not part of the original thread.
What I did is this:
Create a #ManyToMany relationship in JPA and specify a #JoinTable.
Manually define an entity with identical table name to the table specified in 1. For this table, I chose a composite primary key using #IdClass. I also added my attribute.
Inside one of the n:m-connected entities, create a #OneToMany relationship to the connection-table-entity created in 2. However, I did not create a corresponding #ManyToOne relationship as that would have created an error.
As a result, I can access the original entities and their relation as many-to-many, but also the relation itself, which is not an entity in the original ERM, but it is for JPA. First tests show this seems to be working.
I am aware, however, that I basically access the same part of the persistence (the PostgreSQL database) through two different ways at the same time.
Now my questions are:
Is this a valid way to do it? Or will I get in bad trouble at one point?
Is there a situation where I will need to refresh to prevent trouble?
Is this something new in JPA > 2.0, or just an extension to the original answer?
This should help.
Here is how I do it:
#Entity
#Table(name = "person", schema = "crm")
public final class Person implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person", orphanRemoval = true)
private Set<PersonEmail> emails = new HashSet<>();
}
#Entity
#Table(name = "email", schema = "crm")
public final class Email implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#Column(name = "email", nullable = false, length = 64, columnDefinition = "varchar(64)")
private String localPart;
#Column(name = "domain", nullable = false, length = 255, columnDefinition = "varchar(255)")
private String domain;
}
#Entity
#Table(name = "person_email", schema = "crm")
public final class PersonEmail implements Serializable {
#EmbeddedId
private PersonEmailId id;
// The mapped objects are fetched lazily.
// This is a choice.
#ToString.Exclude
#MapsId("personId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Person person;
#ToString.Exclude
#MapsId("emailId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Email email;
// Here's an extra column.
#Column(name = "type", nullable = false, columnDefinition = "email_type_t")
#Convert(converter = EmailType.EmailTypeConverter.class)
private EmailType type;
public final void setPerson(final Person person) {
this.person = person;
id.setPersonId(this.person.getId());
}
public final void setEmail(final Email email) {
this.email = email;
id.setEmailId(this.email.getId());
}
#Embeddable
public static final class PersonEmailId implements Serializable {
#Column(name = "person_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long personId;
#Column(name = "email_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long emailId;
}

Hibernate Jpa custom query

I'm facing a problem that makes me sweat.
first pojo:
#Entity
#Table(name = "generic_params")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", defaultImpl = Void.class)
public class GenericParams extends AbstractParam {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "code", unique = true)
private String code;
#Column(name = "type", insertable = false, updatable = false, unique = true)
private String type;
#JsonManagedReference
#OneToMany(mappedBy = "genericParams", cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
private Set<LabelTranslation> labelTranslations = new HashSet<>();
...getter / setter
second pojo:
#Entity
#Table(name = "label_translation")
public class LabelTranslation extends AbstractParam {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "label")
private String label;
#ManyToOne
private Language language;
#Column(name = "language_id", insertable = false, updatable = false)
private Long languageId;
#ManyToOne(fetch = FetchType.LAZY)
#JsonBackReference
private GenericParams genericParams;
#Column(name = "generic_params_id", insertable = false, updatable = false)
private Long genericParamsId;
GenericParamsMapper
#Mapper(componentModel = "spring", uses = { LabelTranslationMapper.class })
public interface GenericParamsMapper extends GenericParamsParameterizedMapper<GenericParams> {
#Override
GenericParamsDTO toDto(GenericParams genericParams);
#Override
GenericParams toEntity(GenericParamsDTO genericParamsDTO);
#Override
GenericParams fromSuperClassToDerived(GenericParams genericParams);
}
The problem when I make the request to return a type of GenericParams it returns all LabelTranslation without filter.
My request:
#Query(value = " SELECT gparam FROM GenericParams gparam " //
+ " INNER JOIN gparam.labelTranslations label with label.languageId =:lang " //
+ " WHERE gparam.type =:type " //
+ " AND gparam.deleted = false ") //
List<GenericParams> getGenericParamByFilteringLabelTranslation(#Param("lang") Long lg, #Param("type") String tp);
this request is called by :
#Override
public List<GenericParamsDTO> getGenericParamsByType(String type, HttpServletRequest request) {
Long idLang = this.languageService.findIdByCulture(request.getLocale().toLanguageTag());
List<GenericParams> gList = this.genericParamsRepository.getGenericParamByFilteringLabelTranslation(idLang, type);
result this.genericParamsMapper.toDto(gList);
}
Here it is supposed to retrieve a list of GenericParams with the filter on LabelTranslation which have the 2L language for example
except that here it returns all LabelTranslation without filter.
You'll say yes because the mapper ignores the property.
I will tell you that I need to ignore it for other need.
So how do i ignore it for one need and filter it for another.
Getter and setter are omitted in order to simplify
thanks for lending me a hand.
it was more challenging than I thought
by analyzing the queries executed by hibernate, I realized the error.
despite that i don't know how to solve it.
This first request is good:
select gp.* from generic_params gp
inner join label_translation labeltrans1_
on gp.id=labeltrans1_.generic_params_id and (labeltrans1_.language_id=? and labeltrans1_.language_id=?)
where gp.type=?
and gp.is_deleted=0;
but this returns the false information (since it is executed automatically by Hibernate)
so it's a design problem
select * from label_translation lb
left outer join lang lang on lb.language_id=lang.id
where lb.generic_params_id=?
now how do i tell hibernate to execute a request like this?
select label.* from label_translation label
left outer join lang langue on label.language_id=langue.id
where label.generic_params_id=200
and langue.id=2;
you will notice that I just added this piece of code:
and langue.id=2;
So to make a summary, I left things as they are and I added in the mapper a method that filters on LabelTransaltion and I have the right result.
a second option is to make two requests:
- The first one returns the GenericParams
- The second one returns the LabelTransaltion and then in the GenericParams object i set the list of LabelTransaltion by the 2nd request.
But I wonder if there is another more concise hibernate way.
thank you

Cascade persist creates duplicate rows?

I'm creating a database entity object Order, and assign it to multiple entities of type BookingCode.
Problem: this creates a single order in db, which is fine. But the order itself has a #OneToOne OrderDescription, which occurs duplicate in the database.
#Entity
public class BookingCode {
#Id
private Long id;
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH})
private Order order;
}
#Entity
public class Order {
#Id
private Long id;
private String orderName;
#OneToOne(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private OrderDescription description;
}
#Entity
public class OrderDescription {
#Id
private Long id;
//for simplicity just one text element; of course multiple fields in real life
private String text;
#OneToOne
private Order order;
}
Test:
Order order = new Order();
order.setOrderName("test");
OrderDescription d = new OrderDescription("testdescr");
d.setOrder(order);
order.setDescription(d);
List<BookingCodes> codes = new ArrayList<>();
BookingCode code = new BookingCode();
code.setOrder(order);
codes.add(order);
BookingCode code2 = new BookingCode();
code2.setOrder(order); //using the same offer entity!
codes.add(order2);
codes = dao.save(codes); //CrudRepository from Spring
dao.findOne(codes.get(0).getId()); //this works, find an order which has one of the OrderDescriptions
Result:
In my database I then have two OrderDescription entries, where I would expect only one, because I reused the same Order object and assigned it to different BookingCode objects.
Like:
table order_descrption:
1;"de";"testdescr";"123456"
2;"de";"testdescr";"123456"
As Order has a #OneToOne relation to OrderDescription
And I even don't understand why the select using findOne() works correctly. Because in database I now have two OrderDescriptions that map to the same Order, but an Order can only have one of them.
Persist the order first and then assign it to both bookingCode .
I had a similar issue where I had an Order obj and its variable prevOrder was referring to itself i.e. Order entity. And when I stored order, it would end up storing duplicate records for prevOrder.
I had the following code:
#Entity
#Table(name = "orders")
public class Order implements Serializable {
#Id
#GeneratedValue(generator = "order_id_generator")
#SequenceGenerator(name = "order_id_generator", sequenceName = "order_id_sequence", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToOne(cascade = CascadeType.ALL, optional = true)
#JoinColumn(name = "previous_order_id", unique = true, updatable = false, referencedColumnName = "id")
private Order previousOrder;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "previousOrder")
private Order nextOrder;
...
I tried various things including overriding equals and hashcode of Order, and adding a OneToOne mappedBy field 'nextOrder' etc. But noticed JPA didn't even call equals() to determine object's uniqueness. Ultimately I found out that JPA uses id field as the object's identifier and I wasn't storing the generated id while storing the object to a distrobuted cache. So it was all the time creating fresh objects during persistence.

JPA Entity field reference OneToOne recursive

I am getting this error when I will persist() my entity. I think that the cause of the error is the relation, my idea is that FolderEntity (represents a virtual folder) can be stay inside another (only one) Then I created the reference to self (In the extended class, because all resources can be inside a folder, and folder is an resource)
org.hibernate.AnnotationException: Referenced property not a (One|Many)ToOne: com.editor.entity.FolderEntity.id in mappedBy of com.editor.entity.FolderEntity.folderId
This my main Entity:
#MappedSuperclass
public abstract class Entity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", nullable = false)
private Integer id;
/** getter/setter **/
}
Then I extends it in my ResourceEntity Entity:
#MappedSuperclass
public class ResourceEntity extends Entity {
#Column(name = "NAME", length = Lengths.NAME40, unique = true, nullable = false)
private String name;
#Column(name = "DESCRIPTION", length = Lengths.DESCRIPTION1000, unique = false, nullable = true)
private String description;
#JoinColumn(name = "FOLDER_ID", updatable = true, nullable = false)
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "id")
private FolderEntity folderId;
/** getter/setter **/
}
Finally, I am working with this entity:
#javax.persistence.Entity
#Table(name = "EDITOR_FOLDERS")
#NamedQueries({
#NamedQuery(name = FolderEntity.ALL_FOLDERS, query = "select f from FolderEntity f"),
#NamedQuery(name = FolderEntity.FOLDER_BY_NAME, query = "select f from FolderEntity f where name = :name and resourceType = :resourceType") })
public class FolderEntity extends ResourceEntity {
public static final String ALL_FOLDERS = "findAllFolders";
public static final String FOLDER_BY_NAME = "findAllFoldersByName";
#Column(name = "RESOURCE_TYPE", length = Lengths.CODE, unique = false, nullable = false)
private Integer resourceType;
/** getter/setter **/
}
Anybodys help me to solve this? Thanks!
You should check the meaning of mappedBy: It does not reference the field that contains the ID (JPA is clever enough to find that one by itself), but it references another XToOne field that "owns" the mapping
public abstract String mappedBy
(Optional) The field that owns the relationship. This element is only specified on the inverse (non-owning) side of the association.
(from javadoc of OneToOne)
In your case you don't need the mappedBy as you are on the owning side. And you should name the attribute folder as you are referencing no ID but an entity.
Another remark: Use an enum for resourceType if you intend to define the possible values in your application as constants.

JPA merge results in duplicate entry of foreign entity

QUESTIONS:
Does anyone know how to merge without having EntityManager trying to re-insert the foreign entity?
SCENARIO:
Just to set up a scenario that closely matches my case: I have two entities
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
#Entity
#Table(name = "friendshiptype", catalog = "friends")
public class FriendshipType implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "username")
private Login login;
#Column(name = "type", unique = true, length = 32)
private String type;
...//other fields go here
}
Both the Login entity and the FriendshipType entity are persisted to the database separately. Then, later, I need to merge a Login row with a FriendshipType row. When I call entityManager.merge(friendship), it tries to insert a new Login which of course results in the following error
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'myUserName1350319637687' for key 'username'
Error Code: 1062
Call: INSERT INTO friends.login (password, username) VALUES (?, ?)
My question, again, is how do I merge two objects without having enityManager trying to reinsert the foreign object?
Here is how I solve the problem. I finally figure the reason the merge is not resolving is because the login.id is auto generated by JPA. So since I really don't need an auto-generated id field, I remove it from the schema and use username as the #id field:
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
Another solution that occurred to me, which I didn't implement but may help someone else, should they need to have an auto-generated id field.
Instead of creating an instance of Login for the merger, get the instance from the database. What I mean is, instead of
Login login = new Login(); login.setUsername(username); login.setPassword(password);
Do rather
Login login = loginDao.getByUsername(username);
That way, a new id field is not generated making the entity seem different.
Thanks and up-votes to everyone for helping, especially to #mijer for being so patient.
You can make your #JoinColumn non updatable:
#JoinColumn(name = "login_id", updatable = false) // or
#JoinColumn(name = "username", referencedColumnName = "username", updatable= false)
Or try to refresh / fetch your Login entity again before merging the FriendshipType:
// either this
entityManager.refresh(friendship.getLogin());
// or this
final Login login = entityManager
.getReference(Login.class, friendship.getLogin().getId());
friendship.setLogin(login);
// and then
entityManager.merge(friendship);
But, as other suggested I belive that FriendshipType would be better represented by a #ManyToOne relationship or maybe by a Embeddable or ElementCollection
Update
Yet another option is to change the owning side:
public class Login implements java.io.Serializable {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "friendshiptype_id")
private FriendshipType friendshipType;
// Other stuff
}
public class FriendshipType implements java.io.Serializable {
#OneToOne(fetch=FetchType.LAZY, mappedBy="friendshipType")
private Login login;
// Other stuff
}
This will affect your data model (login table will have a friendshiptype_id column instead of the other way around), but will prevent the errors that you are getting, since relationships are always maintained by the owning side.
Have you tried cascade=MERGE? I.e.
#OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
UPDATE
Another possible option is to use #ManyToOne (it's save as the association is unique)
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
You can do it with your original #Id setup. i.e.
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
You can, but you don't need to change to:
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
The trick is you must start by loading from the DB, via em.find(...) or em.createQuery(...). Then the id is guaranteed to be populated with the right value from the DB.
Then you can detach the entity by ending a transaction (for a transaction-scoped entity manager in a session bean), or by calling em.detach(ent) or em.clear(), or by serialising the entity and passing it over the network.
Then you can update the entity, all the while, keeping the original id value.
Then you can call em.merge(ent) and you will still have the correct id. However, I believe the entity must already pre-exist in the persistent context of the entity manager at this instant, otherwise it will think that you have a new entity (with manually populated id), and try to INSERT on transaction flush/commit.
So the second trick is to ensure the entity is loaded at the point of the merge (via em.find(...) or em.query(...) again, if you have a new persistent context and not the original).
:-)

Categories