JPA Query with several different #Id columns - java

Problem
To make my code cleaner i want to introduce a generic Repository that each Repository could extend and therefore reduce the code i have to have in each of them. The problem is, that the Ids differ from Class to Class. On one (see example below) it would be id and in the other randomNumber and on the other may even be an #EmbeddedId. I want to have a derived (or non derived) query in the respository that gets One by id.
Preferred solution
I Imagine having something like:
public interface IUniversalRepository<T, K>{
#Query("select t from # {#entityName} where #id = ?1")
public T findById(K id);
}
Ecample Code
(that does not work because attribute id cannot be found on Settings)
public interface IUniversalRepository<T, K>{
//should return the object with the id, reagardless of the column name
public T findById(K id);
}
// two example classes with different #Id fields
public class TaxRate {
#Id
#Column()
private Integer id;
...
}
public class Settings{
#Id
#Column() //cannot rename this column because it has to be named exactly as it is for backup reason
private String randomNumber;
...
}
// the Repository would be used like this
public interface TaxRateRepository extends IUniversalRepository<TaxRate, Integer> {
}
public interface SettingsRepository extends IUniversalRepository<TaxRate, String> {
}
Happy for suggestions.

The idea of retrieving JPA entities via "id query" is not so good as you might think, the main problem is that is much slower, especially when you are hitting the same entity within transaction multiple times: if flush mode is set to AUTO (with is actually the reasonable default) Hibernate needs to perform dirty checking and flush changes into database before executing JPQL query, moreover, Hibernate doesn't guarantee that entities, retrieved via "id query" are not actually stale - if entity was already present in persistence context Hibernate basically ignores DB data.
The best way to retrieve entities by id is to call EntityManager#find(java.lang.Class<T>, java.lang.Object) method, which in turn backs up CrudRepository#findById method, so, yours findByIdAndType(K id, String type) should actually look like:
default Optional<T> findByIdAndType(K id, String type) {
return findById(id)
.filter(e -> Objects.equals(e.getType(), type));
}
However, the desire to place some kind of id placeholder in JQPL query is not so bad - one of it's applications could be preserving order stability in queries with pagination. I would suggest you to file corresponding CR to spring-data project.

Related

How to count number of rows based on enum value?

I have a database table where one of the columns contain an enum
Entity file:
#Entity
#Table(name = "error_log_entry")
public class ErrorLogEntryEntity extends AbstractPersistable<UUID> {
#Id
private UUID id;
#Column(name = "priority")
#Enumerated(EnumType.STRING)
private ErrorLogEntryPriorityType priority;
}
Enum file
public enum ErrorLogEntryPriorityType {
INFO,
WARN,
DANGER
}
Now I am trying to write a query that counts the number of rows where the enum is equal to "DANGER"
public interface ErrorLogEntryRepository extends JpaRepository<ErrorLogEntryEntity, UUID> {
List<ErrorLogEntryEntity> findByChangedUserId(UUID userId);
#Query(nativeQuery = true, value = "select count(*) from error_log_entry where priority = 'DANGER'")
ErrorLogEntryPriorityType getErrorCount();
}
However, this causes the following error
java.lang.ClassCastException: class java.math.BigInteger cannot be cast to class project.core.types.ErrorLogEntryPriorityType
I am a bit new to spring, but from the documentation, this problem seems to occur because I am casting on an incompatible type. I am not 100% sure.
Any suggestions on how to make this query work?
As mentioned in the comments, the error is because the return type of the query method you've defined is incorrect. You're not looking to get back an instance of ErrorLogEntryPriorityType, you need to get back a numerical value - the count of how may records match the query.
Having said that, there's an even easier, cleaner solution - use Spring Data's "derived query method" and you don't even need to write the #Query yourself. For example, this should work as you intend:
long countByPriority(ErrorLogEntryPriorityType priority);
Note there is no #Query annotation; Spring Data understands the mapping of your entity and generates the query for you based on the method name and parameter types.
This method is also more flexible; the application can use it to count all records with any of the values for priority, not just DANGER. Generally speaking, it's good to have the low-level repository methods be building blocks, like Lego. The application can then assemble a wide variety of "things" from those basic building blocks, such as a service method that counts how many DANGER log records there are.
I suggest you read all the reference docs for Spring Data repositories, starting here.

Aggregate to JPA Entity mapping

In a DDD-project I'm contributing to, we're seeking for some convenient solutions to map entity objects to domain objects and visa versa.
Developers of this project agreed to fully decouple domain model from data model.
The data layer uses JPA (Hibernate) as persistence technology.
As we all reckon that persistence is an implementation detail in DDD, from a developers' point of view we're all seeking for the most appropriate solution in every aspect of the application.
The biggest concern we're having is when an aggregate, containing a list of entities, is mapped to a JPA entity that in it's turn contains a one-to-many relationship.
Take a look at the example below:
Domain model
public class Product extends Aggregate {
private ProductId productId;
private Set<ProductBacklogItem> backlogItems;
// constructor & methods omitted for brevity
}
public class ProductBacklogItem extends DomainEntity {
private BacklogItemId backlogItemId;
private int ordering;
private ProductId productId;
// constructor & methods omitted for brevity
}
Data model
public class ProductJpaEntity {
private String productId;
#OneToMany
private Set<ProductBacklogItemJpaEntity> backlogItems;
// constructor & methods omitted for brevity
}
public class ProductBacklogItemJpaEntity {
private String backlogItemId;
private int ordering;
private String productId;
// constructor & methods omitted for brevity
}
Repository
public interface ProductRepository {
Product findBy(ProductId productId);
void save(Product product);
}
class ProductJpaRepository implements ProductRepository {
#Override
public Product findBy(ProductId productId) {
ProductJpaEntity entity = // lookup entity by productId
ProductBacklogItemJpaEntity backlogItemEntities = entity.getBacklogItemEntities();
Set<ProductBacklogItem> backlogItems = toBackLogItems(backlogItemEntities);
return new Product(new ProductId(entity.getProductId()), backlogItems);
}
#Override
public void save(Product product) {
ProductJpaEntity entity = // lookup entity by productId
if (entity == null) {
// map Product and ProductBacklogItems to their corresponding entities and save
return;
}
Set<ProductBacklogItem> backlogItems = product.getProductBacklogItems();
// how do we know which backlogItems are: new, deleted or adapted...?
}
}
When a ProductJpaEntity already exists in DB, we need to update everything.
In case of an update, ProductJpaEntity is already available in Hibernate PersistenceContext.
However, we need to figure out which ProductBacklogItems are changed.
More specifically:
ProductBacklogItem could have been added to the Collection
ProductBacklogItem could have been removed from the Collection
Each ProductBacklogItemJpaEntity has a Primary Key pointing to the ProductJpaEntity.
It seems that the only way to detect new or removed ProductBacklogItems is to match them by Primary Key.
However, primary keys don't belong in the domain model...
There's also the possibility to first remove all ProductBacklogItemJpaEntity instances (which are present in DB) of a ProductJpaEntity, flush to DB, create new ProductBacklogItemJpaEntity instances and save them to DB.
This would be a bad solution. Every save of a Product would lead to several delete and insert statements in DB.
Which solution exists to solve this problem without making too many sacrifices on Domain & Data model?
You can let JPA/Hibernate solve problem for you.
public void save(Product product) {
ProductJpaEntity entity = convertToJpa(product);
entityManager.merge(entity);
// I think that actually save(entity) would call merge for you,
// if it notices that this entity already exists in database
}
What this will do is:
It will take your newly created JPA Entity and attach it
It will examine what is in database and update all relations accordingly, with priority given to your created entity (if mappings are set correctly)
This is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
Entity views can also be updatable and/or creatable i.e. support flushing changes back, which can be used as a basis for a DDD design.
Updatable entity views implement dirty state tracking. You can introspect the actual changes or flush changed values.
You can define your updatable entity views as abstract classes to hide "implementation specifics" like e.g. the primary key behind the protected modifier like this:
#UpdatableEntityView
#EntityView(ProductJpaEntity.class)
public abstract class Product extends Aggregate {
#IdMapping
protected abstract ProductId getProductId();
public abstract Set<ProductBacklogItem> getBacklogItems();
}
#UpdatableEntityView
#EntityView(ProductBacklogItemJpaEntity.class)
public abstract class ProductBacklogItem extends DomainEntity {
#IdMapping
protected abstract BacklogItemId getBacklogItemId();
protected abstract ProductId getProductId();
public abstract int getOrdering();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
Product p = entityViewManager.find(entityManager, Product.class, id);
Saving i.e. flushing changes is easy as well
entityViewManager.save(entityManager, product);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features and for flushing changes, you can define a save method in your repository that accepts the updatable entity view
I believe you need to address the issue in a different way.
It is really hard to determine which has been changed when you have a complex graph of objects. However, there should be someone else (maybe a service) which really knows what have changed in advance.
In fact, I did not see in your question the real business "Service" or a class which address the business logic. This will be the one who can solve this issue. As a result, you will have in your repository something more specific removeProductBacklogItem(BacklogItemId idToRemove) or... addProductBacklogItem(ProductId toProductId, ProductBacklogItem itemToAdd). That will force you to manage and identify changes in other way... and the service will be responsible for.

How to save entities with manually assigned identifiers using Spring Data JPA?

I'm updating an existing code that handles the copy or raw data from one table into multiple objects within the same database.
Previously, every kind of object had a generated PK using a sequence for each table.
Something like that :
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
In order to reuse existing IDs from the import table, we removed GeneratedValue for some entities, like that :
#Id
#Column(name = "id")
private Integer id;
For this entity, I did not change my JpaRepository, looking like this :
public interface EntityRepository extends JpaRepository<Entity, Integer> {
<S extends Entity> S save(S entity);
}
Now I'm struggling to understand the following behaviour, within a spring transaction (#Transactional) with the default propagation and isolation level :
With the #GeneratedValue on the entity, when I call entityRepository.save(entity) I can see with Hibernate show sql activated that an insert request is fired (however seems to be only in the cache since the database does not change)
Without the #GeneratedValue on the entity, only a select request is fired (no insert attempt)
This is a big issue when my Entity (without generated value) is mapped to MyOtherEntity (with generated value) in a one or many relationship.
I thus have the following error :
ERROR: insert or update on table "t_other_entity" violates foreign key constraint "other_entity_entity"
Détail : Key (entity_id)=(110) is not present in table "t_entity"
Seems legit since the insert has not been sent for Entity, but why ? Again, if I change the ID of the Entity and use #GeneratedValue I don't get any error.
I'm using Spring Boot 1.5.12, Java 8 and PostgreSQL 9
You're basically switching from automatically assigned identifiers to manually defined ones which has a couple of consequences both on the JPA and Spring Data level.
Database operation timing
On the plain JPA level, the persistence provider doesn't necessarily need to immediately execute a single insert as it doesn't have to obtain an identifier value. That's why it usually delays the execution of the statement until it needs to flush, which is on either an explicit call to EntityManager.flush(), a query execution as that requires the data in the database to be up to date to deliver correct results or transaction commit.
Spring Data JPA repositories automatically use default transactions on the call to save(…). However, if you're calling repositories within a method annotated with #Transactional in turn, the databse interaction might not occur until that method is left.
EntityManager.persist(…) VS. ….merge(…)
JPA requires the EntityManager client code to differentiate between persisting a completely new entity or applying changes to an existing one. Spring Data repositories w ant to free the client code from having to deal with this distinction as business code shouldn't be overloaded with that implementation detail. That means, Spring Data will somehow have to differentiate new entities from existing ones itself. The various strategies are described in the reference documentation.
In case of manually identifiers the default of inspecting the identifier property for null values will not work as the property will never be null by definition. A standard pattern is to tweak the entities to implement Persistable and keep a transient is-new-flag around and use entity callback annotations to flip the flag.
#MappedSuperclass
public abstract class AbstractEntity<ID extends SalespointIdentifier> implements Persistable<ID> {
private #Transient boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
// More code…
}
isNew is declared transient so that it doesn't get persisted. The type implements Persistable so that the Spring Data JPA implementation of the repository's save(…) method will use that. The code above results in entities created from user code using new having the flag set to true, but any kind of database interaction (saving or loading) turning the entity into a existing one, so that save(…) will trigger EntityManager.persist(…) initially but ….merge(…) for all subsequent operations.
I took the chance to create DATAJPA-1600 and added a summary of this description to the reference docs.

How do I stop spring data JPA from doing a SELECT before a save()?

We are writing a new app against an existing database. I'm using Spring Data JPA, and simply doing a
MyRepository.save()
on my new entity, using
MyRepository extends CrudRepository<MyThing, String>
I've noticed in the logs that hibernate is doing a Select before the insert, and that they are taking a long time, even when using the indexes.
I've searched for this here, and the answers I've found usually are related to Hibernate specifically. I'm pretty new to JPA and it seems like JPA and Hibernate are pretty closely intertwined, at least when using it within the context of Spring Data. The linked answers suggest using Hibernate persist(), or somehow using a session, possibly from an entityManager? I haven't had to do anything with sessions or entityManagers, or any Hibernate API directly. So far I've gotten simple inserts done with save() and a couple #Query in my Repositories.
Here is the code of Spring SimpleJpaRepository you are using by using Spring Data repository:
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
It does the following:
By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity will be assumed as new, otherwise as not new.
Link to Spring Data documentation
And so if one of your entity has an ID field not null, Spring will make Hibernate do an update (and so a SELECT before).
You can override this behavior by the 2 ways listed in the same documentation. An easy way is to make your Entity implements Persistable (instead of Serializable), which will make you implement the method "isNew".
If you provide your own id value then Spring Data will assume that you need to check the DB for a duplicate key (hence the select+insert).
Better practice is to use an id generator, like this:
#Entity
public class MyThing {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private UUID id;
}
If you really must insert your own id and want to prevent the select+insert then implement Persistable, e.g.
#Entity
public class MyThing implements Persistable<UUID> {
#Id
private UUID id;
#Override
public UUID getId() {
return id;
}
//prevent Spring Data doing a select-before-insert - this particular entity is never updated
#Override
public boolean isNew() {
return true;
}
}
I created a custom method in the #Repository:
public void persistAll(Iterable<MyThing> toPersist) {
toPersist.forEach(thing -> entityManager.persist(thing));
}
If you provide your own ID value then Spring Data will assume that you need to check the DB for a duplicate key (hence the select+insert).
One option is to use a separate autogenerated ID column as Primary key but this option seems redundant. Because if you already have a Business/Natural ID that is unique then it is easier to make this as the #ID column instead of having a separate ID column.
So how to solve the problem?
The solution is to use #javax.persistence.Version on a new versionNumber column in all the tables. If you have a parent and child table then use #Version column in all the entity classes.
Add a column in the Entity class like this:
#javax.persistence.Version
#Column(name = "data_version")
private Long dataVersion;
add column in SQL file:
"data_version" INTEGER DEFAULT 0
Then I see that Spring data does not do Select before doing Insert.

Hibernate/JPA version concurrency control and DTO/change command patterns

I would like to use #Version for optimistic concurrency control with JPA & Hibernate.
I know how it works in the typical scenario of two parallel transactions. I also know that if I have a CRUD with 1:1 mapping between the form and entity, I can just pass version along as a hidden field and use this to prevent concurrent modifications by users.
What about more interesting cases, which use DTOs or change command patterns? Is it possible to use #Version in this scenario as well, and how?
Let me give you an example.
#Entity
public class MyEntity {
#Id private int id;
#Version private int version;
private String someField;
private String someOtherField;
// ...
}
Now let's say two users open the GUI for this, make some modifications and save changes (not at the same time, so the transactions don't overlap).
If I pass the entire entity around, the second transaction will fail:
#Transactional
public void updateMyEntity(MyEntity newState) {
entityManager.merge(newState);
}
That's good, but I don't like the idea of passing entities everywhere and sometimes would use DTOs, change commands etc.
For simplicity change command is a map, eventually used in a call like this on some service:
#Transactional
public void updateMyEntity(int entityId, int version, Map<String, Object> changes) {
MyEntity instance = loadEntity(entityId);
for(String field : changes.keySey()) {
setWithReflection(instance, field, changes.get(field));
}
// version is unused - can I use it somehow?
}
Obviously, if two users open my GUI, both make a change, and execute it one after another, in this case both changes will be applied, and the last one will "win". I would like this scenario to detect concurrent modification as well (the second user should get an exception).
How can I achieve it?
If I understand your question correctly, all you need is a setter for your private int version field and when you update the entity, you set it in your entity. Of course your DTO must always transport version data. Eventually, you would do also something like:
MyEntity instance = loadEntity(entityId);
entityManager.detach(instance);
for(String field : changes.keySey()) {
setWithReflection(instance, field, changes.get(field));
}
//set also the version field, if the loop above does not set it
entityManager.merge(instance);

Categories