This may be a simple question, but I'm trying to find out if there is a way that I can create a JPQL update query that would allow me to update a single Persisted Entity using a unique column identifier that is not the primary key.
Say I have and entity like the following:
#Entity
public class Customer {
#ID
private Long id;
#Column
private String uniqueExternalID;
#Column
private String firstname;
....
}
Updating this entity with a Customer that has the id value set is easy, however, id like to update this customer entity using the uniqueExternalId without having to pre-query for the local entity and merge the changes in or manually construct a jpql query with all the fields in it manually.
Something like
UPDATE Customer c SET c = :customer WHERE c.uniqueExternalId = :externalId
Is something like this possible in JQPL?
You cannot do it in the exact way you describe - by passing an entity reference, but you can use bulk queries to achieve the same effect.
UPDATE Customer c SET c.name = :name WHERE c.uniqueExternalId = :externalId
Please note that you will have to explicitly define each updated attribute.
It is important to note that bulk queries bypass the persistence context. Entity instances that are managed within the persistence context will not reflect the changes to the records that are changed by the bulk update. Further, if you use optimistic locking, consider incrementing the #Version field of your entities with the bulk update:
UPDATE Customer c SET c.name = :name, c.version = c.version + 1 WHERE c.uniqueExternalId = :externalId
EDIT: The JPA 2.0 spec advises in ยง 4.10:
In general, bulk update and delete operations should only be performed
within a transaction in a new persistence context or before fetching
or accessing entities whose state might be affected by such
operations.
Related
I would like someone to explain me why Hibernate is making one extra SQL statement in my straight forward case. Basically i have this object:
#Entity
class ConfigurationTechLog (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
val configurationId: Long,
val type: String,
val value: String?
) {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "configurationId", insertable = false, updatable = false)
val configuration: Configuration? = null
}
So as you can see, nothing special there. And when i execute this query :
#Query(value = "SELECT c FROM ConfigurationTechLog c where c.id = 10")
fun findById10() : Set<ConfigurationTechLog>
In my console i see this:
Hibernate:
/* SELECT
c
FROM
ConfigurationTechLog c
where
c.id = 10 */ select
configurat0_.id as id1_2_,
configurat0_.configuration_id as configur2_2_,
configurat0_.type as type3_2_,
configurat0_.value as value4_2_
from
configuration_tech_log configurat0_
where
configurat0_.id=10
Hibernate:
select
configurat0_.id as id1_0_0_,
configurat0_.branch_code as branch_c2_0_0_,
configurat0_.country as country3_0_0_,
configurat0_.merchant_name as merchant4_0_0_,
configurat0_.merchant_number as merchant5_0_0_,
configurat0_.org as org6_0_0_,
configurat0_.outlet_id as outlet_i7_0_0_,
configurat0_.platform_merchant_account_name as platform8_0_0_,
configurat0_.store_type as store_ty9_0_0_,
configurat0_.terminal_count as termina10_0_0_
from
configuration configurat0_
where
configurat0_.id=?
Can someone please explain me, what is happening here ? From where this second query is coming from ?
I assume you are using Kotlin data class. The kotlin data class would generate toString, hashCode and equals methods utilizing all the member fields. So if you are using the returned values in your code in a way that results in calling of any of these method may cause this issue.
BTW, using Kotlin data claases is against the basic requirements for JPA Entity as data classes are final classes having final members.
In order to make an association lazy, Hibernate has to create a proxy instance instead of using the real object, i.e. it needs to create an instance of dynamically generated subclass of the association class.
Since in Kotlin all classes are final by default, Hibernate cannot subclass it so it has to create the real object and initialize the association right away. In order to verify this, try declaring the Configuration class as open.
To solve this without the need to explicitly declare all entities open, it is easier to do it via the kotlin-allopen compiler plugin.
This Link can be useful for understand what kind (common) problem is that N + 1 Problem
Let me give you an example:
I have three Courses and each of them have Students related.
I would like to perform a "SELECT * FROM Courses". This is the first query that i want (+ 1) but Hibernate in background, in order to get details about Students for each Course that select * given to us, will execute three more queries, one for each course (N, there are three Course coming from select *). In the end i will see 4 queries into Hibernate Logs
Considering the example before, probably this is what happen in your case: You execute the first query that you want, getting Configuration Id = 10 but after, Hibernate, will take the entity related to this Configuration, then a new query is executed to get this related entity.
This problem should be related in specific to Relationships (of course) and LAZY Fetch. This is not a problem that you have caused but is an Hibernate Performance Issue with LAZY Fetch, consider it a sort of bug or a default behaviour
To solve this kind of problem, i don't know if will be in your case but ... i know three ways:
EAGER Fetch Type (but not the most good option)
Query with JOIN FETCH between Courses and Students
Creating EntityGraph Object that rappresent the Course and SubGraph that rappresent Students and is added to EntityGraph
Looking at your question, it seems like an expected behavior.
Since you've set up configuration to fetch lazily with #ManyToOne(fetch = FetchType.LAZY), the first sql just queries the other variables. When you try to access the configuration object, hibernate queries the db again. That's what lazy fetching is. If you'd like Hibernate to use joins and fetch all values at once, try setting #ManyToOne(fetch = FetchType.EAGER).
I have an old database where there are two tables with implicit association between them:
booking
- id
- name
... some other fields...
and
booking_info
- id
- booking_id
... some other fields...
Due to the current database design there no any constraints between these two tables, which means that one booking entry may exist without any booking_info entries and vice versa. booking_id in booking_info table is an implicit foreign key which refers to booking table (column id), but it also may refer to the absent booking.
I have created the following JPA mapping for these tables:
#Entity
public class Booking {
#Id
private Long id;
private String name;
// getters & setters
}
and
#Entity
public class BookingInfo {
#Id
private Long id;
#OneToOne
#JoinColumn(name = "booking_id")
private Booking booking
// getters & setters
}
Now I need to be able to persist a BookingInfo entity even if there's no related Booking entry in the database.
BookingInfo bookingInfo = new BookingInfo();
bookingInfo.setId(1);
Booking booking = new Booking();
booking.setId(182); // let's say that there's no booking with id 182 in my database, but I still need to persist this bookingInfo
bookingInfo.setBooking(booking);
bookingInfoRepository.save(bookingInfo); // using Spring Data JPA
If I run this code then I get javax.persistence.EntityNotFoundException since booking with id 182 is absent.
What would be the proper workaround for my case using JPA or Hibernate.
Btw, I also tried to use Hibernate's #NotFound annotation. As a result, save method doesn't throw javax.persistence.EntityNotFoundException and entity gets persisted int the database, but the problem is that booking_id in the database always null.
Any help would be appreciated.
I am not sure my answer will help you or not, but the result you are getting perfectly make sense. As you are setting a JPA object, and that object is not present, hence the null value is saved.
If you want to save 182 as an integer, you don't do JPA relationship. Instead, you just use booking-id as an integer field in booking-info. And that makes sense because you actually do not have the relationship between those tables which the JPA is trying to achieve.
But I am sure you just want to save 182 and as well as maintain the JPA relationship. And I am sure you already know it, but DB integrity is not being maintained with the approach you are taking. I am sure there is enough reason behind that. But my recommendation would be applying proper constraints in the DB and then in JPA.
I'm trying to understand EclipseLink behaviour in case if I use native query. So I have Entity like this:
class Entity {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="other_entity_id")
private OtherEntity otherEntity;
#Column(name = "name")
private String name;
//gets ... sets ...
}
and corresponding table looks like:
**ENTITY**
INTEGER ID;
VARCHAR NAME;
OTHER_ENTITY_ID;
And then I run native query
Query query = getEntityManager().runNativeQuery("select * from ENTITY", Entity.class);
query.getResultList()
Within Entity I have declared OtherEntity otherEntity which is annotated with FetchType.LAZY, however my query selects (*) - all of the columns, including OTHER_ENTITY_ID. The question is - if I run native query that fetches all columns, will fields annotated with FetchType.LAZY populated as if they were FetchType.EAGER or not? I've never worked with EclipseLink before and tyring to decide is it worth using it or not so I would really appreciate any help
Thanks, Cheers
My first advice is to turn on EclipseLink's SQL logging, and execute the equivalent JPQL to load what you are looking for and see the SQL EclipseLink generates to accomplish that to get an understanding of what is required to build objects in your native queries based on your current mappings.
Relationships generally loaded with a secondary query using the values read in from the foreign keys, so eager or lazy fetching is not affected by the native query to read in "Entity" - the query requires the other_entity_id value regardless of the fetch type. When required based on eager/lazy loading, EclipseLink will issue the query required by the mapping.
You can change this though by marking that the relationship is to use joining. In this case, EclipseLink will expect not only the Entity values to be in the query, but the referenced OtherEntity values as well.
I have an Entity (say Employee) and a find method which uses TypedQuery to execute a named query and return the Employee rows. When the properties of this returned Employee instance is changed it is persisted.
I am trying to figure out the JPA concept behind this and how is this different from update. Is it good to update single row like this if only few column values of the existing rows in db needs change.
Looking for pointers to JPA concept that explains this.
Here is the code snip.
#Entity
#NamedQueries({
#NamedQuery(name = "Employee.findInActiveEmployee", query = "SELECT e FROM Employee e" +
"WHERE some_prop = :something")
})
public class Employee implements Serializable {
#Id
#NotNull
#Column(name = "id")
private String id;
#Column(name = "name")
private int name;
//so and so properties
//getter and setters
}
the finder method
TypedQuery<Employee> query = getEntityManager().
createNamedQuery("Employee.findInActiveEmployee", Employee.class);
query.setParameter("someproperty", "somevalue");
try {
return query.getSingleResult();
} catch (NoResultException e) {
throw new NoSuchObjectException("somevalue");
}
It's not really different from update.
In JPA you usually don't need to explicitly merge changes, since the JPA implementation will keep track of what data has changed in managed objects (i.e. entities the EntityManager knows about, such as ones that it has just loaded for you) and will make sure to save those changes to the underlying database.
If you don't want that, you can explicitly detach the entity with em.detach(Object o);, so the EntityManager no longer manages it . After that you'll need to perform merge() to update any changes.
Entities you get back from JPQL-Queries are managed by the EntityManager. In other words, they are atached and there is no need to merge them (like you would need to do for detached entities).
If you alter the entities you got back from the query and you have an open transaction the changes will be committed back to the database.
If you want to update a large number of entities a at the same time or your entities contain some members that have a really large serialized footprint then it might pay of performance-wise to use JPQL Updates.
I am trying to establish a relationship between 2 entities which would be zero-to-one. That is, the Parent can be saved without the associated Child entity and also along with the assoicated Child.
Following are the 2 Entity classes...
Employee (Parent)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="EMP_NAME")
private String name;
#PrimaryKeyJoinColumn
#OneToOne(cascade = {CascadeType.ALL})
private EmployeeInfo info;
#Column(name="EMP_ENUM")
private Integer enumId;
EmployeeInfo (Child)
public class EmployeeInfo {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(name="EMPLOYEE_EMAIL")
private String email;
With such kind of a relation and id column of the only Parent (Employee) table set to AUTO INCREMENT in MySql DB, the problem is that while saving a Parent->Child object graph, I get the following exception
org.springframework.orm.hibernate3.HibernateJdbcException: JDBC exception on Hibernate data access: SQLException for SQL [insert into EMP_INFO
Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
I tried setting the Child Table's Id property to AUTO INCREMENT in the DB , and the persistence of such a Parent->Child object graph is successful.
However, the problem described here surfaces, because I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object, and hence do NOT want to have AUTO INCREMENT on the Child's id column.
One solution could be not use the PrimaryKeyJoinColumn, but use a particular JoinColumn, but that adds an unnecessary column to my existing Table.
Has anyone come across such a problem? If yes, any pointers would be much helpful.
Finally, I got it working thanks to Pascal and some googling from my side. Apparently, I cannot use the Native key generator for such relationships where the parent can exist without the child (optional = true).
The thing that worked finally was the following, leaving me the downside of having to deal with Hibernate specific annotation (#GenericGenerator) and also having to make-do with bi-directional relationships instead of the unidirectional that I wanted.
Employee (Parent) class remains unchanged as above. It has AUTO INCREMENT on the Id column.
As for the child class (EmployeeInfo) it changed to the following, and again WITHOUT having the AUTO INCREMENT set on the Id column.
#Table(name="EMP_INFO")
#Entity
public class EmployeeInfo {
#Id
#GeneratedValue(generator="foreign")
#GenericGenerator(name="foreign", strategy = "foreign", parameters={
#Parameter(name="property", value="verifInfo")})
private Long id;
#OneToOne(optional=false)
#JoinColumn (name="id")
private Employee emp;
#Column(name="EMPLOYEE_EMAIL")
private String email;
This helped me achieve what I wanted but on the downside, GenericGenerator is not a JPA annotation, it is a hibernate annotation, and sadly I have to make do with that as of now because JPA does not currently support this(or any similar) annotation.
Anyway, it helps to get through such cases :-)
I have a scenario in which I would like to save the parent (Employee) object without the associated EmpInfo object.
The optional attribute of a OneToOne is true by default, which is what you want.
However, you are somehow misusing the #PrimaryKeyJoinColumn here (well, it actually depends on what you really want to achieve but your current combination of annotations is not correct).
IF you want to map a OneToOne with a shared primary-key, use the #PrimaryKeyJoinColumn. But in that case, don't use a GeneratedValue on EmployeeInfo and set the id manually or, if you don't want to set it manually, use the Hibernate specific foreign generator that I already mentioned in your previous question. Check also the related question mentioned below.
And IF you do not want to use a shared primary key (like in your current code since you're trying to get the id generated by the database), then do not use the PrimaryKeyJoinColumn.
You have to make a choice.
References
JPA 1.0 specification:
9.1.32 PrimaryKeyJoinColumn Annotation
Related question
JPA Hibernate One-to-One relationship.