Define #Query on base repository interface - spring

Is there a way to declare a #Query on a base repsitory interface so that you don't have to declare it in all repositories? The query would have different entity names in the "FROM" part of the query.
#MappedSuperclass
public abstract class BaseAction {
#Id
Long id;
...
}
#Entity
#Table(name="AKTION_EMAIL")
public class EmailAction extends BaseAction {
private String email;
}
public interface ActionRepository<T extends BaseAction> extends JpaRepository<T, ActionPK> {
#Query("SELECT max(seqNumber) + 1 FROM ????????????? e WHERE e.order = ?1 AND e.action = ?2")
Long findNextSeqNumberByOrderAndAction(Order order, ActionConfiguration action);
}
public interface EmailActionRepository extends ActionRepository<EmailAction> {
// This works, but I don't want to repeat that in all entity repositories...
#Query("SELECT max(seqNumber) + 1 FROM EmailAction e WHERE e.order = ?1 AND e.action = ?2")
Long findNextSeqNumberByOrderAndAction(Order order, ActionConfiguration action);
}

You can use a SpEL expression in the generic query definition to refer to the unknown entity type:
interface ActionRepository<T extends BaseAction> extends JpaRepository<T, ActionPK> {
#Query("SELECT max(seqNumber) + 1 FROM #{#entityName} e WHERE …")
Long findNextSeqNumberByOrderAndAction(Order ActionConfiguration action);
}
Note how we use #{#entityName} to dynamically insert the name of the entity that re repository will be created for.

Related

How to fix typing in hibernate "findBy" for enums?

I have an entity like so
public class SomeClass {
#NonNull
#NotNull
#Column(nullable = false)
#Convert(converter = SomeEnumConverter.class)
private SomeEnum fieldName;
}
I want to be able to find row in DB where the name of the enum matches to the string/enum which I pass. This table has only 2 columns. UUID which is PK and fieldName which is a varchar. I tried the following repository methods.
public interface SomeClassRepository extends AnotherRepository<SomeClass, UUID> {
Optional<SomeClass> findByFieldName(SomeEnum param);
}
public interface SomeClassRepository extends AnotherRepository<SomeClass, UUID> {
Optional<SomeClass> findByFieldName(String param);
}
My problem is if I try to find by passing a String (2nd case) it complains that my passed value is not the expected type(SomeEnum).
On the other hand if I pass the enum directly, It tries to look for rows with PK(UUID) = enum which I passed instead of searching on the other column(fieldName varchar). How do I get past this ?
public class SomeClass {
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private SomeEnum fieldName;
}
public interface SomeClassRepository extends AnotherRepository<SomeClass, UUID> {
Optional<SomeClass> findByFieldName(SomeEnum param);
}
Please try this. This should work

(Spring/JpaRepository ) Dynamic #Query, when Inheriting methods of JpaRepository from BaseEntityRepository to SubEntityRepository

(This post continues the discussion from, the important parts are all repeated in-here:
(Spring/JpaRepository ) Inheriting methods of JpaRepository from BaseEntityRepository to SubEntityRepository)
Let's assume we have the following entities:
#Entity public class BaseEntity { }
#Entity public class SubEntity extends BaseEntity { }
and the following JpaRepository implemenations:
public interface BaseEntityRepository<T, I> extends JpaRepository<TableWithId, Long> {
#Query("SELECT t FROM BaseEntity t WHERE id = :id")
Optional<T> getById(#Param("id") Long id);
#Query("SELECT t FROM BaseEntity t WHERE customField = :customField")
List<T> findByCustomField(#Param("customField") String customField);
}
Now for the SubEntity I have another repo:
public interface SubEntityRepository extends BaseEntityRepository<SubEntity, Long> {}
Will JPA know that in #Query "BaseEntity" must be replaced by "SubEntity" and why?
If "no", how would be the best way to do what I want?
You need to use SPEL
#Query("SELECT T FROM #{#entityName} T WHERE T.id = :id")
Optional<T> getById(#Param("id") Long id);

Entity mapping to DTO

I want to map query result to DTO of the following JPQL:
#Repository
public interface FooRepository extends JpaRepository<Foo, Id> {
#Query("select f.game, sum(f.timeSpent) as duration from foo f group by f.game order by duration desc")
List<Foo> findMostPlayable();
}
As a result, I receive list of objects which consist of GameCatalog object and Long number:
0 = {Object[2]#10670}
0 = {GameCatalog#10675}
1 = {Long#10676} 8968
Foo.class looks like:
#Entity
#Getter
#Setter
public class Foo{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "game_catalog_id", nullable = false)
private GameCatalog game;
private Long timeSpent;
}
I plan to use MapStruct to map model with DTO but I cannot do that since 'findMostPlayable' returns result in such a way above.
How I can implement mapping here?
And should I use JPA JPQL way or hibernate features like projections and so on?
To get result of query with aggregate function,you can create your own class instead of using entity and use that.
Ex.
public class MyFoo {
private String game;
private int duration;
public String getGame() {
return game;
}
public void setGame(String game) {
this.game = game;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
}
#Repository
public interface FooRepository extends JpaRepository<Foo, Id> {
#Query("select new MyFoo(f.game as game, sum(f.timeSpent) as duration ) from foo f group by f.game order by duration desc")
List<MyFoo> findMostPlayable();
}

Spring Data JPA remove child entities

I have a load repository.
#Transactional
public interface MyLoadRepository extends CrudRepository<ParentEntity, Serializable> {
}
Then is my ParentEntity.
#MappedSuperclass
public class ParentEntity {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
#Column(name = "id", unique = true)
private String uuid;
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
}
Then I have multiple child entities.
#Entity
#Table(name = "EntityA")
public class EntityA extends ParentEntity {
}
#Entity
#Table(name = "EntityB")
public class EntityB extends ParentEntity {
}
Ques : I want to delete these entities separately by my repository.
If I write something like this.
#Autowired
private MyLoadRepository repository;
and then repository.deleteAll()
I get error that repository is not entity (It obiviously not).
Here I want to delete either entityA or entityB data completely based on some condition. How can I do that ?
We should create repository per entity and not on non entity classes.
So, for your case you need 2 repository classes
#Transactional
public interface EntityARepo extends CrudRepository< EntityA, String> {
}
#Transactional
public interface EntityBRepo extends CrudRepository< EntityB, String> {
}
now in service classes you can do
#Autowired
private EntityARepo repoA;
#Autowired
private EntityBRepo repoB;
and then you can call delete method based on your condition
repoA.deleteAll()
or
repoB.deleteAll()
You need to fetch the entity based on a condition. For example, if the EntityA has a primary key uuid, then you must find EntityA by uuid and then delete the EntityA.
EntityA entityA = entityARepo.findOne(uuid);
repository.delete(entityA);
EntityB entityB = entityBRepo.findOne(uuid);
repository.delete(entityB);

How to fetch only selected attributes of an entity using Spring JPA?

I'm using Spring Boot (1.3.3.RELEASE) and Hibernate JPA in my project. My entity looks like this:
#Data
#NoArgsConstructor
#Entity
#Table(name = "rule")
public class RuleVO {
#Id
#GeneratedValue
private Long id;
#Column(name = "name", length = 128, nullable = false, unique = true)
private String name;
#Column(name = "tag", length = 256)
private String tag;
#OneToMany(mappedBy = "rule", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RuleOutputArticleVO> outputArticles;
#OneToMany(mappedBy = "rule", cascade = CascadeType.ALL, orphanRemoval = true)
private List<RuleInputArticleVO> inputArticles;
}
My repository looks like this:
#Repository
public interface RuleRepository extends JpaRepository<RuleVO, Long> {
}
In some cases I need to fetch only id and name attributes of entity RuleVO. How can I achieve this? I found a notice it should be doable using Criteria API and Projections but how? Many thanks in advance. Vojtech
UPDATE:
As has been pointed out to me, I'm lazy and this can very well be done hence I'm updating my answer after having looked around the web for a proper one.
Here's an example of how to get only the id's and only the names:
#Repository
public interface RuleRepository extends JpaRepository<RuleVO, Long> {
#Query("SELECT r.id FROM RuleVo r where r.name = :name")
List<Long> findIdByName(#Param("name") String name);
#Query("SELECT r.name FROM RuleVo r where r.id = :id")
String findNameById(#Param("id") Long id);
}
Hopefully this update proves helpful
Old Answer:
Only retrieving the specific attributes name/id is not possible as this is not how spring was designed or any SQL database for that matter as you always select a row which is an entity.
What you CAN do is query over the variables in the entity, for instance:
#Repository
public interface RuleRepository extends JpaRepository<RuleVO, Long> {
public RuleVo findOneByName(String name);
public RuleVo findOneByNameOrId(String name, Long id);
public List<RuleVo> findAllByName(String name);
// etc, depending on what you want
}
You can modify these however you want w.r.t. your needs. You can call these methods directly via the autowired repository
See http://docs.spring.io/spring-data/jpa/docs/current/reference/html/ Section 5.3 for more options and examples
interface IdOnly{
String getId();
}
#Repository
public interface RuleRepository extends JpaRepository<RuleVO, Long> {
public List<IdOnly> findAllByName(String name);
}
I notice that this is a very old post, but if someone is still looking for an answer, try this. It worked for me.
You can also define custom constructor to fetch specific columns using JPQL.
Example:
Replace {javaPackagePath} with complete java package path of the class
use as a constructor in JPQL.
public class RuleVO {
public RuleVO(Long id, String name) {
this.id = id;
this.name = name;
}
}
#Repository
public interface RuleRepository extends JpaRepository<RuleVO, Long> {
#Query("SELECT new {javaPackagePath}.RuleVO(r.id, r.name) FROM RuleVo r where r.name = :name")
List<RuleVO> findIdByName(#Param("name") String name);
}
Yes, you can achieve it with projections. You have many ways to apply them:
If you could upgrade to Spring Data Hopper, it provides an easy to use support for projections. See how to use them in the reference documentation.
Otherwise, first of all create a DTO with the attributes you want to load, something like:
package org.example;
public class RuleProjection {
private final Long id;
private final String name;
public RuleProjection(Long id, String name) {
this.id = id;
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
Of course, you could use Lombok annotations also.
Then, you can use in the JPQL queries like this:
select new org.example.RuleProjection(rule.id, rule.name) from RuleVO rule order by rule.name
Another option, if you want to avoid using DTO class names in your queries, is to implement your own query method using QueryDSL. With Spring Data JPA, you have to:
Create a new interface with the new method. Ex:
public interface RuleRepositoryCustom {
public List<RuleProjection> findAllWithProjection();
}
Change your repository to extend the new interface. Ex:
public interface RuleRepository extends JpaRepository<RuleVO, Long>, RuleRepositoryCustom {
...
Create an implementation of the Custom repository using the Spring Data JPA QueryDSL support. You have to previously generate the Q clases of QueryDSL, using its Maven plugin. Ex:
public class RuleRepositoryImpl {
public List<RuleProjection> findAllWithProjection() {
QRuleVO rule = QRuleVO.ruleVO;
JPQLQuery query = getQueryFrom(rule);
query.orderBy(rule.name.asc());
return query.list(ConstructorExpression.create(RuleProjection.class, rule.id, rule.name));
}
}
You can do it by using #Query annotation(HQL).
Please refer to the Spring docs below:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query
(search for #Query in spring document)

Resources