Database: MySQL 5.1
JPA implementation: EclipseLink
Container: Glassfish 4
Java: JDK 7u55
I use a native query to get results from a table of orders. Each order has a date and I want to select only by year and date. The whole method has this body.
Query query = entityManager.createNativeQuery("SELECT COUNT(PLACED_ORDER.ID) as POCET_OBJEDNAVEK FROM PLACED_ORDER WHERE ORDERDATE IS NOT NULL AND EXTRACT(YEAR FROM ORDERDATE) = ?1 AND extract(DAY FROM ORDERDATE) = ?2");
query.setParameter(1, year);
query.setParameter(2, day);
return (Long) query.getSingleResult();
This query WORKS properly, but is very slow to execute. By slow, I mean a second or more for every method call. Transaction management is set do Required.
Named queries are executed within few milliseconds, measured from request to response. Calling this method alone is very slow. Is there something that can be done about it ?
EDIT - Reaction to comments:
When I query the database from command line or via MySQL WorkBench, the performance is normal (max a millisecond or a few under heavy load).
I should also metion that SECOND LEVEL CACHE is disabled on the project and there is nothing I can do about it.
MySQL Explain looks like this (there is no easy way for me to enable explain on EclipseLink).
Entity annotations used to determine data type and DB constraints.
#Entity(name = "PLACED_ORDER")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name = "CUSTOMER_ID")
private Customer customer;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE, CascadeType.REMOVE, CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(name = "PLACED_ORDER_ID")
private List<OrderItem> items;
#Temporal(TemporalType.TIMESTAMP)
private Date orderDate;
#Enumerated(EnumType.STRING)
#NotNull
private OrderState orderState = OrderState.SEMIFINISHED;
#OneToOne
private Transportation transportation;
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
private Address deliveryAddress;
#Enumerated(EnumType.STRING)
private TransportationType transportationType;
With technologies specified in the question and second level cache disabled, the solution was to write the native query as #NamedNativeQuery, instead of creating it every time method was called. This way, the performance significantly increased.
Related
I add to my Entity
#Version
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "version")
private Date version;
and something strange happend. When i update, hibernate tells thath key already exists. How #version filed affect my Entity? I have no idea why it happend. When i remove this #version field everything works. I also use #Audited annotation.
My Entity:
private static final long serialVersionUID = 1636824190907788517L;
#Id
#NotNull
#Column(name = "id")
private UUID id;
#Version
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "version")
private Date version;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "user", nullable = false)
private User user;
#Column(name = "purpose", length = 100)
protected String comment;
#OneToOne(cascade = CascadeType.REMOVE)
#JoinColumn(name = "eq_id", nullable = false)
protected BasicEquipment equip;
#OneToOne(cascade = CascadeType.REMOVE)
#JoinColumn(name = "eq_id2", nullable = false)
protected BasicEquipment equip2;
Error:
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement (...)
org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(Standaorg.hibernate.engine.jdbc.sporg.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatch
org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTra
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "pk_entity"
Did you try this solution?
http://www.byteslounge.com/tutorials/jpa-entity-versioning-version-and-optimistic-locking
Under some circumstances, problems may occur when versioned updates
are used together with batch updates. It has happened to me in the
past with a given version of Oracle 11g and Hibernate, for example.
The Oracle JDBC driver was not able to extract the correct number of
updated rows count in JDBC batch statements execution.
If you are also facing this problem, you may check if you have set the
Hibernate property hibernate.jdbc.batch_versioned_data to true. When
this setting is true, Hibernate will use batch updates even for
updates that are made against versioned data, ie. updates that need to
use the updated rows count in order to check for concurrent updates.
Since the JDBC driver may not return the correct updated rows count,
Hibernate will not be able to detect if concurrent updates actually
happened. The default value for this setting in Hibernate is false, so
it will not use batch updates when it detects that versioned data
updates are going to be executed in a given flush operation.
This is of course a specific scenario with Oracle 11g that is easily
worked around, as we have just seen.
I'm currently a little blocked with this and I can't see it clearly.
So I hope one of you have good idea's to help me.
The important code at the moment :
#Entity
#Table(name = "T_NOTA_RECIPIENT")
public class NotaRecipient extends PersistentEntity {
#Id
#Column(name = "NOTA_RECIPIENT_SID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "STATUS", insertable = true, updatable = true)
#Enumerated(EnumType.STRING)
private Status status = Status.NEW;
#ManyToOne
#JoinColumn(name = "NOTA_SID", referencedColumnName = "NOTA_SID", nullable = false)
private Nota nota;
#ManyToOne
#JoinColumn(name = "CREATOR_OFFICE_SID", referencedColumnName = "OFFICE_SID", nullable = false)
private Office creator;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "notaRecipient")
private Set<FollowUp> followUps;
...
}
Now, actually I don't want to load all the FollowUp who are in the DB but just the one of the current user.
But the problem is that I want to include the FollowUp so I can do database paging query.
We use hibernate, Spring Data and Query DSL with BooleanBuilder to "refine" our search.
I was thinking of using #Formula but this need to be a constant String so I can't include current userId in that.
Second solution could be setting the FollowUp as #Transient and fetch it myself in the DB and set it in mine service.
Problem here is that I can't use it as filter then or ordering by it.
#Formula doesn't have so much documentation, so is it possible to make a #Transient user and use that in the #Formula?
I asked some colleagues but they couldn't help me.
So then it's the time for asking here.
I can get the current user in the API, so that's no problem.
Anybody have alternative solutions?
You can define a mapping with expression
#JoinColumnOrFormula(formula=#JoinFormula(value="(SELECT f.id
FROM follow_up_table f
WHERE f.nota_id=id
and f.user_id={USER_ID})",
referencedColumnName="...")
And then add hibernate interceptor (see the example) and change the SQL on fly replacing {USER_ID} with real value in the
/**
* Called when sql string is being prepared.
* #param sql sql to be prepared
* #return original or modified sql
*/
public String onPrepareStatement(String sql);
I'm having a hard time understanding this JPA behavior which to me doesn't seem to follow the specification.
I have 2 basic entities:
public class User {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#OrderBy("sequence ASC")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.REMOVE })
private final Set<UserProfile> userprofiles = new HashSet<UserProfile>(0);
//Ommiting rest of fields since they aren't relevant
}
public class UserProfile {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "userID", nullable = false, foreignKey = #ForeignKey(name = "FK_UserProfile_User"))
private User user;
//Ommiting rest of fields since they aren't relevant
}
As you can see I only have cascading set to REMOVE, the behavior will be the same if I don't have cascade set at all.
Now if I call:
User user = new User();
user.setId(UUIDGenerator.generateId());
UserProfile userProfile = new UserProfile();
userProfile.setId(UUIDGenerator.generateId());
userProfile.setUser(user);
user.getUserProfiles().add(userProfile);
em.merge(user);
merge will throw an exception.
I see Hibernate is executing a SQL query against the UserProfile table:
select userprofil0_.userProfileID as userProf1_4_0_, userprofil0_.profileID as profileI3_4_0_, userprofil0_.sequence as sequence2_4_0_, userprofil0_.userID as userID4_4_0_ from UserProfile userprofil0_ where userprofil0_.userProfileID=?
And then it will throw an exception
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.mytest.domain.UserProfile with id 6aaab891-872d-41e6-8362-314601324847;
Why is this query even called?
Since I don't have cascade type set to MERGE in userprofiles my expectation would be that JPA/Hibernate would simply ignore the entities inside userprofiles set and only insert/update the user record, doesn't this go against the JPA specs?
If I change cascadetype to MERGE things will work as expected and both User and UserProfile will be added to the database, so no problem there. What puzzles me is why is Hibernate querying the database and erroring out about an entity that's not supposed to be merged at all since I don't have it set to cascade.
This is more of an academic scenario that I ran into, of course I could simply clear the userprofiles set and things would work, but I'm trying to understand why the above behavior happens since I'm probably missing some crucial piece of information about how merge works. It seems it will always try to attach all entities to the session regardless cascade type being set or not.
Why is this query even called?
It's because you are trying to merge the entity, in JPA merge() is used to make the entity managed/attached. To "merge" User, JPA needs to still maintian the references it holds(UserProfile). In your case its not trying to persist UserProfile its trying to get a reference to it to merge User. Read here
If you use persist rather than merge this should not happen.
I have two tables with 'one to many' relationship. I use Jpa + Spring JpaRepository. Sometimes I have to get object from Database with internal object. Sometimes I dont't have to. Repositories always return object with internal objects.
I try to get 'Owner' from Database and I always get Set books; It's OK. But when I read fields of this internal Book , I get LazyInitializationException. How to get null instead of Exception?
#Entity
#Table(name = "owners")
#NamedEntityGraph(name = "Owner.books",
attributeNodes = #NamedAttributeNode("books"))
public class Owner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "owner_id", nullable = false, unique = true)
private Long id;
#Column(name = "owner_name", nullable = false)
private String name;
#OneToMany(fetch = FetchType.LAZY,mappedBy = "owner")
private Set<Book> books= new HashSet<>(0);
public Worker() {
}
}
#Entity
#Table(name = "books")
#NamedEntityGraph(name = "Book.owner",
attributeNodes = #NamedAttributeNode("owner"))
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "book_id", unique = true, nullable = false)
private Long id;
#Column(name = "book_name", nullable = false, unique = true)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private Owner owner;
public Task() {
}
}
public interface BookRepository extends JpaRepository<Book,Long>{
#Query("select t from Book t")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
List<Book> findAllWithOwner();
#Query("select t from Book t where t.id = :aLong")
#EntityGraph(value = "Book.owner", type = EntityGraph.EntityGraphType.LOAD)
Book findOneWithOwner(Long aLong);
}
You are getting LazyInitializationException because you are accessing the content of the books Set outside the context of a transaction, most likely because it's already closed. Example:
You get an Owner from the database with your DAO or Spring Data repository, in a method in your Service class:
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You try to access the Set here
return owner;
}
At this point you have an Owner object, with a books Set which is empty, and will only be populated when someone wants to access its contents. The books Set can only be populated if there is an open transaction. Unfortunately, the findOne method has opened and already closed the transaction, so there's no open transaction and you will get the infamous LazyInitializationException when you do something like owner.getBooks().size().
You have a couple of options:
Use #Transactional
As OndrejM said you need to wrap the code in a way that it all executes in the same transaction. And the easiest way to do it is using Spring's #Transactional annotation:
#Transactional
public Owner getOwner(Integer id) {
Owner owner = ownerRepository.findOne(id);
// You can access owner.getBooks() content here because the transaction is still open
return owner;
}
Use fetch = FetchType.EAGER
You have fetch = FecthType.LAZY in you #Column definition and that's why the Set is being loaded lazily (this is also the fetch type that JPA uses by default if none is specified). If you want the Set to be fully populated automatically right after you get the Owner object from the database you should define it like this:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "owner")
private Set<Book> books= new HashSet<Book>();
If the Book entity is not very heavy and every Owner does not have a huge amount of books it's not a crime to bring all the books from that owner from the database. But you should also be aware that if you retrieve a list of Owner you are retrieving all the books from all those owners too, and that the Book entity might be loading other objects it depends on as well.
The purpose of LazyInitializationException is to to raise an error when the loaded entity has lost connection to the database but not yet loaded data which is now requested. By default, all collections inside an entity are loaded lazily, i.e. at the point when requested, usually by calling an operation on them (e.g. size() or isEmpty()).
You should wrap the code that calls the repository and then works with the entity in a single transaction, so that the entity does not loose connection to DB until the transaction is finished. If you do not do that, the repository will create a transaction on its own to load the data, and close the transaction right after. Returned entity is then without transaction and it is not possible to tell, if ots collections have some elements or not. Instead, LazyInitializationException is thrown.
I've got a question about Spring JPA (JPQL and sorts).
Been searching my ass of for this and i can't seem to find an answer.
If there is any duplicate, please let me know. I haven't come across a question/tutorial/guide that fits my needs.
Okay so i got Spring with JPA. And i have 2 entities.
Asset and AssetStatus. An Asset could have multiple AssetStatuses, one AssetStatus belongs to an Asset.
The entities are build as following. I omitted the unneeded properties.
Asset:
#Entity
#Table(name = "T_ASSET")
public class Asset implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(mappedBy = "asset", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private List<AssetStatus> assetStatus;
}
AssetStatus:
#Entity
#Table(name = "T_ASSETSTATUS")
public class AssetStatus implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
#JsonSerialize(using = CustomLocalDateTimeSerializer.class)
#JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
#Column(name = "timestamp", nullable = false, insertable = true, updatable = true)
private LocalDateTime timestamp;
#JsonIgnore
#NotNull
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Asset asset;
}
And i got a standard AssetRepository. (Ignore the find by barcode, its an omitted property from Asset.)
public interface AssetRepository extends JpaRepository<Asset, Long>, QueryDslPredicateExecutor<Asset> {
Asset findOneByBarcode(String barcode);
}
Now i want to get all my assets, with the last AssetStatus they had. Which is defined by the timestamp in AssetStatus. Getting all assets works but i also get all AssetStatuses they have (or had). I just want the most recent.
How do i manage to do this? I tried writing a custom query with #Query. Or name a method like findLastAssetStatus() or something. All didn't work.
Is there someone who could help me figure this out? I am willing to give more information and answer any questions asked.
EDIT: I found out the query in MySQL to get the wanted result (Works in MySQL Workbench on my database):
SELECT * FROM T_ASSET asset JOIN T_ASSETSTATUS assetStatus WHERE asset.id = assetStatus.asset_id AND assetStatus.timestamp = (SELECT max(timestamp) FROM T_ASSETSTATUS WHERE asset_id = asset.id);
As i use this in my AssetRepository with #query nativequery=true (As specified below), it does not give me the wanted result. This will still give me every status with every assetStatus it has ever had.
#Query(value = "SELECT * FROM T_ASSET asset JOIN T_ASSETSTATUS assetStatus WHERE asset.id = assetStatus.asset_id AND assetStatus.timestamp = (SELECT max(timestamp) FROM T_ASSETSTATUS WHERE asset_id = asset.id)", nativeQuery = true)
List<Asset> findAssetWithlastStatus();
How do i write this query in JPQL? Or solve this in any other way?
Thanks in advance!
Cheers, Clemenz
You can take a look at Hibernate filters. Write a filter which will filter out the old statuses and enable it in the sessions in which you want it to be applied.