Spring batch RepositoryItemWriter not working - spring-data-jpa

I have a scenario where a given json file Employee.json needs to be read to Employee object and then written to employee table in mysql database in a batch process.
Employee.json:
[
{
"firstName": "John",
"lastName":"Snow",
"age":30
},
{
"firstName": "Tony",
"lastName": "Stark",
"age": 40
},
{
"firstName": "Peter",
"lastName":"Parker",
"age":35
}
]
Employee.java :
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Data
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private int age;
}
I used the JsonItemReader for reading the file from my resource location and RepositoryItemWriter for persisting the employee object.
This is how my batch config looks:
#Configuration
#ComponentScan(basePackages = "za.co.sanlam.employee")
#EnableBatchProcessing
#EnableTransactionManagement
public class DataInsertJsontoMysqlJob {
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
PlatformTransactionManager platformTransactionManager;
#Autowired
LocalContainerEntityManagerFactoryBean entityManagerFactory;
#Value("input/Employee.json")
private Resource inputjson;
#Bean(name = "insertDataJob")
public Job job(#Qualifier("step1") Step step1) {
return jobBuilderFactory.get("insertDataJob").incrementer(new RunIdIncrementer()).start(step1).build();
}
#Bean
protected Step step1(ItemWriter writer) throws ParseException {
return stepBuilderFactory
.get("step1")
.transactionManager(platformTransactionManager)
.<Employee, Employee>chunk(2)
.reader(jsonItemReader())
.writer(writer)
.build();
}
#Bean
public JsonItemReader<Employee> jsonItemReader() {
return new JsonItemReaderBuilder<Employee>()
.jsonObjectReader(new JacksonJsonObjectReader<>(Employee.class))
.resource(inputjson)
.name("jsonItemReader")
.build();
}
#Bean
public RepositoryItemWriter<Employee> writer(EmployeeRepository repository) {
final RepositoryItemWriter<Employee> repositoryItemWriter = new RepositoryItemWriter<>();
repositoryItemWriter.setRepository(repository);
repositoryItemWriter.setMethodName("save");
return repositoryItemWriter;
}
}
Even though the item writer code is executed no data is getting persisted to the Employee table on my local mysql database.
The application is able to connect to the database successfully and all the spring batch related tables are getting populated.
I was able to write the data to the Employee table if i use JdbcBatchItemWriter instead of RepositoryItemWriter:
#Bean //WORKING
public JdbcBatchItemWriter<Employee> writer(DataSource dataSource) {
JdbcBatchItemWriter<Employee> itemWriter = new JdbcBatchItemWriter<>();
itemWriter.setDataSource(dataSource);
itemWriter.setSql("insert into Employee (firstName,lastName,age) values (:firstName,:lastName,:age)");
itemWriter.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider());
itemWriter.afterPropertiesSet();
return itemWriter;
}
I am not including my databaseconfiguration class here.
What am i missing while using RepositoryItemWriter ?

I guess EmployeeRepository uses a JPA-based implementation. If this is the case, the cause of the issue would be that the platformTransactionManager injected on your step is of type DataSourceTransactionManager while it should be JpaTransactionManager.
Make sure you are using a JpaTransactionManager on your step and job repository when using RepositoryItemWriter.

Related

MongoDB client side encryption is not encrypting data

I'm trying to implement the MongoDB client side auto encryption/decryption.
Below a part of the code.
I started manually the mongocryptd and everything looks fine (the mongocryptd is correctly detected on the app startup).
When I call saveCustomer(customer) of the service, the saved data is NOT encrypted and no data is created in the __keyVault collection.
Configuation:
#RequiredArgsConstructor
#EnableTransactionManagement
#Configuration
public class MongodbConfig extends AbstractReactiveMongoConfiguration {
private final DatabaseConfig databaseConfig;
#Override
public MongoClient reactiveMongoClient() {
byte[] masterKey = new byte[96];
try (FileInputStream fis = new FileInputStream(databaseConfig.getEncryptionLocalMasterKey())) {
fis.read(masterKey, 0, 96);
Map<String, Map<String, Object>> kmsProviders = Map.of("local", Map.of("key", masterKey));
String keyVaultNamespace = databaseConfig.getDatabase()+".__keyVault";
// MongoCLient
AutoEncryptionSettings encryptionSettings = AutoEncryptionSettings.builder()
.keyVaultNamespace(keyVaultNamespace)
.kmsProviders(kmsProviders)
.bypassAutoEncryption(false)
.build();
return super.createReactiveMongoClient(MongoClientSettings.builder()
.autoEncryptionSettings(encryptionSettings)
.applyConnectionString(new ConnectionString(databaseConfig.getUri()))
.build());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Entity:
#Data
#Document
#Encrypted(keyId = "xKVup8B1Q+CkHaVRx+qa+g==")
public class Customer {
#Id private String id;
#Indexed(unique = true)
#Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
private String email;
#Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
private String firstName;
#Encrypted(algorithm = "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic")
private String lastName;
}
Service:
#Service
#RequiredArgsConstructor
public class MyService {
private final MongoOperations operations;
public Mono<Customer> saveCustomer(Customer customer) {
return operations.save(customer);
}
}

how to store PostgreSQL jsonb using SpringBoot + JPA?

I'm working on a migration software that will consume unknown data from REST services.
I already think about use MongoDB but I decide to not use it and use PostgreSQL.
After read this I'm trying to implement it in my SpringBoot app using Spring JPA but I don't know to map jsonb in my entity.
Tried this but understood nothing!
Here is where I am:
#Repository
#Transactional
public interface DnitRepository extends JpaRepository<Dnit, Long> {
#Query(value = "insert into dnit(id,data) VALUES (:id,:data)", nativeQuery = true)
void insertdata( #Param("id")Integer id,#Param("data") String data );
}
and ...
#RestController
public class TestController {
#Autowired
DnitRepository dnitRepository;
#RequestMapping(value = "/dnit", method = RequestMethod.GET)
public String testBig() {
dnitRepository.insertdata(2, someJsonDataAsString );
}
}
and the table:
CREATE TABLE public.dnit
(
id integer NOT NULL,
data jsonb,
CONSTRAINT dnit_pkey PRIMARY KEY (id)
)
How can I do this?
Note: I don't want/need an Entity to work on. My JSON will always be String but I need jsonb to query the DB
Tried this but understood nothing!
To fully work with jsonb in Spring Data JPA (Hibernate) project with Vlad Mihalcea's hibernate-types lib you should just do the following:
1) Add this lib to your project:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.2.2</version>
</dependency>
2) Then use its types in your entities, for example:
#Data
#NoArgsConstructor
#Entity
#Table(name = "parents")
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = SEQUENCE)
private Integer id;
#Column(length = 32, nullable = false)
private String name;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private List<Child> children;
#Type(type = "jsonb")
#Column(columnDefinition = "jsonb")
private Bio bio;
public Parent(String name, List children, Bio bio) {
this.name = name;
this.children = children;
this.bio = bio;
}
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Child implements Serializable {
private String name;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Bio implements Serializable {
private String text;
}
Then you will be able to use, for example, a simple JpaRepository to work with your objects:
public interface ParentRepo extends JpaRepository<Parent, Integer> {
}
parentRepo.save(new Parent(
"parent1",
asList(new Child("child1"), new Child("child2")),
new Bio("bio1")
)
);
Parent result = parentRepo.findById(1);
List<Child> children = result.getChildren();
Bio bio = result.getBio();
You are making things overly complex by adding Spring Data JPA just to execute a simple insert statement. You aren't using any of the JPA features. Instead do the following
Replace spring-boot-starter-data-jpa with spring-boot-starter-jdbc
Remove your DnitRepository interface
Inject JdbcTemplate where you where injecting DnitRepository
Replace dnitRepository.insertdata(2, someJsonDataAsString ); with jdbcTemplate.executeUpdate("insert into dnit(id, data) VALUES (?,to_json(?))", id, data);
You were already using plain SQL (in a very convoluted way), if you need plain SQL (and don't have need for JPA) then just use SQL.
Ofcourse instead of directly injecting the JdbcTemplate into your controller you probably want to hide that logic/complexity in a repository or service.
There are already several answers and I am pretty sure they work for several cases. I don't wanted to use any more dependencies I don't know, so I look for another solution.
The important parts are the AttributeConverter it maps the jsonb from the db to your object and the other way around. So you have to annotate the property of the jsonb column in your entity with #Convert and link your AttributeConverter and add #Column(columnDefinition = "jsonb") as well, so JPA knows what type this is in the DB. This should already make it possible to start the spring boot application. But you will have issues, whenever you try to save() with the JpaRepository. I received the message:
PSQLException: ERROR: column "myColumn" is of type jsonb but
expression is of type character varying.
Hint: You will need to rewrite or cast the expression.
This happens because postgres takes the types a little to serious.
You can fix this by a change in your conifg:
datasource.hikari.data-source-properties: stringtype=unspecified
datasource.tomcat.connection-properties: stringtype=unspecified
Afterwards it worked for me like a charm, and here is a minimal example.
I use JpaRepositories:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface MyEntityRepository extends JpaRepository<MyEntity, Integer> {
}
The Entity:
import javax.persistence.Column;
import javax.persistence.Convert;
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
#Convert(converter = MyConverter.class)
#Column(columnDefinition = "jsonb")
private MyJsonObject jsonContent;
}
The model for the json:
public class MyJsonObject {
protected String name;
protected int age;
}
The converter, I use Gson here, but you can map it however you like:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class MyConverter implements AttributeConverter<MyJsonObject, String> {
private final static Gson GSON = new Gson();
#Override
public String convertToDatabaseColumn(MyJsonObject mjo) {
return GSON.toJson(mjo);
}
#Override
public MyJsonObject convertToEntityAttribute(String dbData) {
return GSON.fromJson(dbData, MyJsonObject.class);
}
}
SQL:
create table my_entity
(
id serial primary key,
json_content jsonb
);
And my application.yml (application.properties)
datasource:
hikari:
data-source-properties: stringtype=unspecified
tomcat:
connection-properties: stringtype=unspecified
For this case, I use the above tailored converter class, you are free to add it in your library. It is working with the EclipseLink JPA Provider.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.log4j.Logger;
import org.postgresql.util.PGobject;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Map;
#Converter
public final class PgJsonbToMapConverter implements AttributeConverter<Map<String, ? extends Object>, PGobject> {
private static final Logger LOGGER = Logger.getLogger(PgJsonbToMapConverter.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
#Override
public PGobject convertToDatabaseColumn(Map<String, ? extends Object> map) {
PGobject po = new PGobject();
po.setType("jsonb");
try {
po.setValue(map == null ? null : MAPPER.writeValueAsString(map));
} catch (SQLException | JsonProcessingException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
throw new IllegalStateException(ex);
}
return po;
}
#Override
public Map<String, ? extends Object> convertToEntityAttribute(PGobject dbData) {
if (dbData == null || dbData.getValue() == null) {
return null;
}
try {
return MAPPER.readValue(dbData.getValue(), new TypeReference<Map<String, Object>>() {
});
} catch (IOException ex) {
LOGGER.error("Cannot convert JsonObject to PGobject.");
return null;
}
}
}
Usage example, for an entity named Customer.
#Entity
#Table(schema = "web", name = "customer")
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Convert(converter = PgJsonbToMapConverter.class)
private Map<String, String> info;
public Customer() {
this.id = null;
this.info = null;
}
// Getters and setter omitted.
If you're using R2DBC you can use dependency io.r2dbc:r2dbc-postgresql, and use type io.r2dbc.postgresql.codec.Json in your member attributes of an entity class, e.g.:
public class Rule {
#Id
private String client_id;
private String username;
private String password;
private Json publish_acl;
private Json subscribe_acl;
}

Spring JPA Repository is unable to create mapping tables of my entities

I'm using spring data with H2 database and it's not creating database tables for my entity objects.
application.properties
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:file:~/app_db
spring.datasource.username=sa
spring.datasource.password=sa
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create
DomainApplication.java
#SpringBootApplication
#Import(DefaultPersistenceConfig.class)
#EntityScan(basePackages = {"com.sample.Domain.modal"})
public class DomainApplication {
public static void main(String[] args) {
SpringApplication.run(DomainApplication.class, args);
}
}
DefaultPersistanceConfig:
#Configuration
#EnableJpaRepositories(basePackages = {"com.sample.Domain.model" }, entityManagerFactoryRef = "defaultEntityManagerFactory", transactionManagerRef = "defaultTransactionManager")
#EnableTransactionManagement
public class DefaultPersistenceConfig {
#Autowired
public DataSource defaultDataSource;
#Bean
#Primary
#Qualifier("defaultEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean defaultEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(defaultDataSource).persistenceUnit("hib-unit")
.packages("com.sample.Domain.model").build();
}
#Bean
#Primary
#Qualifier("defaultTransactionManager")
public PlatformTransactionManager defaultTransactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
Entity class:
#Entity
#Table(name = "GENERAL_LEDGER_TRANSACTION")
public class GeneralLedgerTransaction implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id = 0L;
....
JPA Repository interface:
#Repository
public interface GeneralLedgerTransactionRepository extends JpaRepository<GeneralLedgerTransaction, Long> {
}
When I run the application and make a rest call to the controller to fetch data from the repository, it should return an empty array but I keep getting an error below.
org.h2.jdbc.JdbcSQLException: Table "GENERAL_LEDGER_TRANSACTION" not found; SQL statement:
select ... from GENERAL_LEDGER_TRANSACTION generalled0_ [42102-197]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:357) ~[h2-1.4.197.jar:1.4.197] ...
add spring.datasource.initialize=true in your application.properties

JPA generates negative sequence values generated for ID

I am creating a CRUD web application using JPA.
Technically every thing is working fine (no errors at all), but when checking my database I noticed that the after adding a new entry to my table, the ID generated from a sequence is a negative value: -46, -45, -44, etc ...
Here are the relevant parts of my code :
My entity :
#Entity
#NamedQuery(name="Book.findAll", query="SELECT b FROM Book b")
#SequenceGenerator(name="ma_seq", sequenceName="book_seq")
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ma_seq")
#Id private long id;
private String auteur;
private String langue;
private String titre;
public Book() {
}
//...getters and setters
}
My DAO :
public class MyDAO {
//Constructeur
public MyDAO(){
}
#PersistenceContext
private EntityManager em;
#Resource
private UserTransaction userTransaction;
public EntityManager getEm() {
return em;
}
public void setEm(EntityManager em) {
this.em = em;
}
#Transactional
public void register(Book livre) throws NotSupportedException, SystemException, SecurityException, IllegalStateException, RollbackException, HeuristicMixedException, HeuristicRollbackException {
// Save employee
userTransaction.begin();
this.em.persist(livre);
userTransaction.commit();
}
//other fonctions
}
after adding this to #SequenceGenerator then working fine!!!
allocationSize = 1

Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection

I have an entity class as below:
#Entity
public class UserDemo implements Serializable {
#Id
private Long id;
private String username;
private String createdBy;
#Version
private int version;
/***
*
* Getters and setters
*/
}
Using Spring Data JPA and Querydsl how do I fetch a page of UserDemo with only id and username properties populated? I need to use paging as well as searching. In short I would like to achieve the same result as
Page<UserDemo> findAll(Predicate predicate, Pageable pageable);
but with limited field of UserDemo populated.
Looks like custom repository implementation is the way to go for now until something similar available in spring data.
I have gone through http://static.springsource.org/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.custom-implementations
Here is my implementation which works. However it would be good to have this method available directly in Spring-Data-JPA
Step 1: Intermediate interface for shared behavior
public interface CustomQueryDslJpaRepository <T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
/**
* Returns a {#link org.springframework.data.domain.Page} of entities matching the given {#link com.mysema.query.types.Predicate}.
* This also uses provided projections ( can be JavaBean or constructor or anything supported by QueryDSL
* #param constructorExpression this constructor expression will be used for transforming query results
* #param predicate
* #param pageable
* #return
*/
Page<T> findAll(FactoryExpression<T> factoryExpression, Predicate predicate, Pageable pageable);
}
Step 2: Implementation of intermediate interface
public class CustomQueryDslJpaRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
implements CustomQueryDslJpaRepository<T, ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public CustomQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQueryDslJpaRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager,
EntityPathResolver resolver) {
super(entityInformation, entityManager);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public Page<T> findAll(FactoryExpression<T> factoryExpression, Predicate predicate, Pageable pageable) {
JPQLQuery countQuery = createQuery(predicate);
JPQLQuery query = querydsl.applyPagination(pageable, createQuery(predicate));
Long total = countQuery.count();
List<T> content = total > pageable.getOffset() ? query.list(factoryExpression) : Collections.<T> emptyList();
return new PageImpl<T>(content, pageable, total);
}
}
Step 3: Create a custom repository factory to replace the default
public class CustomQueryDslJpaRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomQueryDslJpaRepositoryFactory(entityManager);
}
private static class CustomQueryDslJpaRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private EntityManager entityManager;
public CustomQueryDslJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new CustomQueryDslJpaRepositoryImpl<>(getEntityInformation(metadata.getDomainType()), entityManager);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomQueryDslJpaRepository.class;
}
}
}
Step 4: Use the custom repository factory
Using annotation
#EnableJpaRepositories(repositoryFactoryBeanClass=CustomQueryDslJpaRepositoryFactoryBean.class)
OR using XML
<repositories base-package="com.acme.repository" factory-class="com.acme.CustomQueryDslJpaRepositoryFactoryBean" />
Note: Don't place custom repository interface and implementation in the same directory as base-package. If you are placing then exclude them from scanning otherwise spring will try to create beans for them
Sample usage
public interface UserDemoRepository extends CustomQueryDslJpaRepository<UserDemo, Long>{
}
public class UserDemoService {
#Inject
UserDemoRepository userDemoRepository;
public Page<User> findAll(UserSearchCriteria userSearchCriteria, Pageable pageable) {
QUserDemo user = QUserDemo.userDemo;
return userDemoRepository.findAll(Projections.bean(UserDemo.class, user.id, user.username), UserPredicate.defaultUserSearch(userSearchCriteria), pageable);
}
}
For more recent versions of Spring Data, I couldn't get the accepted answer to work without hitting issues, but found that going down the route from the Spring Data docs, does work by revising that answer as follows:
1. The repository interface
#NoRepositoryBean
public interface QueryDslPredicateAndProjectionExecutor<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
<PROJ> Page<PROJ> customFindWithProjection(FactoryExpression<PROJ> factoryExpression, Predicate predicate, Pageable pageable);
}
2. The repository implementation
public class QueryDslJpaEnhancedRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
implements QueryDslPredicateAndProjectionExecutor<T, ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> builder;
private final Querydsl querydsl;
public QueryDslJpaEnhancedRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public QueryDslJpaEnhancedRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager,
EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
this.path = resolver.createPath(entityInformation.getJavaType());
this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public <PROJ> Page<PROJ> customFindWithProjection(FactoryExpression<PROJ> factoryExpression, Predicate predicate, Pageable pageable) {
JPQLQuery countQuery = createQuery(predicate);
JPQLQuery query = querydsl.applyPagination(pageable, createQuery(predicate));
Long total = countQuery.count();
List<PROJ> content = total > pageable.getOffset() ? query.list(factoryExpression) : Collections.<PROJ>emptyList();
return new PageImpl<PROJ>(content, pageable, total);
}
}
3. Setting the default repository implementation
#EnableJpaRepositories(
repositoryBaseClass=QueryDslJpaEnhancedRepositoryImpl.class,
basePackageClasses=SomeRepository.class)
For the current versions of Spring Data (1.11.1) and QueryDSL (4), you have to change the customFindWithProjection method implementation like this:
#Override
public <PROJ> Page<PROJ> customFindWithProjection(FactoryExpression<PROJ> factoryExpression, Predicate predicate, Pageable pageable) {
final JPQLQuery<?> countQuery = createCountQuery(predicate);
JPQLQuery<PROJ> query = querydsl.applyPagination(pageable, createQuery(predicate).select(factoryExpression));
long total = countQuery.fetchCount();
List<PROJ> content = pageable == null || total > pageable.getOffset() ? query.fetch() : Collections.<PROJ> emptyList();
return new PageImpl<PROJ>(content, pageable, total);
}
The rest of the code remains the same.
I was able to achieve the same result with just a bit of code.
public class Queryable<T> extends QuerydslJpaPredicateExecutor<T> {
private static final EntityPathResolver resolver = SimpleEntityPathResolver.INSTANCE;
private final Querydsl querydsl;
public Queryable(Class<T> domainClass, EntityManager entityManager) {
this(JpaEntityInformationSupport.getEntityInformation(domainClass, entityManager),
entityManager);
}
private Queryable(JpaEntityInformation<T, ?> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager, resolver, null);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<?> builder = new PathBuilder<>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
public Page<T> findAll(Expression<T> expression, Predicate predicate, Pageable pageable) {
JPQLQuery<?> countQuery = createQuery(predicate);
JPQLQuery<T> query = querydsl.applyPagination(pageable,
createQuery(predicate).select(expression));
List<T> dtos = query.fetch();
return PageableExecutionUtils.getPage(dtos, pageable, countQuery::fetchCount);
}
}
Usage:
#Repository
#Transactional
class UserDemoRepository
private static final QUserDemo q = QUserDemo.userDemo;
private static final QBean<UserDemo> PROJECTION = Projections.bean(UserDemo.class,
q.id, q.username);
#PersistenceContext
private EntityManager entityManager;
public Page<UserDemo> findAll(Predicate predicate, Pageable pageable) {
return new Queryable<UserDemo>(UserDemo.class, entityManager)
.findAll(PROJECTION, predicate, pageable);
}
}
(inspired by https://stackoverflow.com/a/53960209/1833472)
As a (albeit very ugly and inefficient) workaround, I simply retrieved a regular Page containing the entities from my repository and manually mapped them to a projection in the controller like so:
#GetMapping(value = "/columns")
public Page<ColumnProjection> getColumns(#QuerydslPredicate(root = Column.class) final Predicate predicate,
final Pageable pageable) {
Page<Column> filteredColumns = columnRepository.findAll(predicate, pageable);
List<ColumnProjection> filteredColumnProjections = new ArrayList<>();
filteredColumns.forEach(c -> filteredColumnProjections.add(new ColumnProjectionImpl(c)));
return new PageImpl<>(filteredColumnProjections, pageable, filteredColumnProjections.size());
}
Where ColumnProjectionImpl is a class implementing my ColumnProjection interface.
This was the easiest solution I could come up with while not having to adapt my existing ColumnRepository.
I've just run into the same issue myself.
In short - we have to specify custom repository factory bean, that tell to use our custom repo as another "fragment".
So i overrided factory.getRepositoryFragments to include a custom projection predicate implementation(IMHO it resolves the issue in the question No property found for type… custom Spring Data repository).
updated code, based on all previous answers :
1.QuerydslPredicateProjectionRepositoryFactory
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFragment;
import javax.persistence.EntityManager;
import java.io.Serializable;
import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
public class QuerydslPredicateProjectionRepositoryFactory extends JpaRepositoryFactory {
private final EntityManager entityManager;
private EntityPathResolver entityPathResolver;
public QuerydslPredicateProjectionRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
this.entityPathResolver = SimpleEntityPathResolver.INSTANCE;
}
#Override
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryComposition.RepositoryFragments fragments = super.getRepositoryFragments(metadata);
boolean isQueryDslRepository = QUERY_DSL_PRESENT
&& QuerydslPredicateProjectionRepository.class.isAssignableFrom(metadata.getRepositoryInterface());
if (isQueryDslRepository) {
JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(metadata.getDomainType());
Object querydslFragment = getTargetRepositoryViaReflection(QuerydslPredicateProjectionRepositoryImpl.class, entityInformation,
entityManager, entityPathResolver, null);
fragments = fragments.append(RepositoryFragment.implemented(querydslFragment));
}
return fragments;
}
}
2.QuerydslPredicateProjectionRepositoryFactoryBean
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
public class QuerydslPredicateProjectionRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> {
public QuerydslPredicateProjectionRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new QuerydslPredicateProjectionRepositoryFactory(entityManager);
}
}
3.QuerydslPredicateProjectionRepository here we add new methods, that use projections etc...
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.List;
public interface QuerydslPredicateProjectionRepository<T> {
<Projection> Page<Projection> findAll(Predicate predicate, Pageable pageable, FactoryExpression<Projection> factoryExpression);
<Projection> List<Projection> findAll(Predicate predicate, Sort sort, FactoryExpression<Projection> factoryExpression);
<Projection> List<Projection> findAll(Predicate predicate, FactoryExpression<Projection> factoryExpression);
}
4.QuerydslPredicateProjectionRepositoryImpl here we implement the repository interface methods
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.CrudMethodMetadata;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.jpa.repository.support.QuerydslJpaPredicateExecutor;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import javax.persistence.EntityManager;
import java.util.List;
public class QuerydslPredicateProjectionRepositoryImpl<T> extends QuerydslJpaPredicateExecutor<T> implements QuerydslPredicateProjectionRepository<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final Querydsl querydsl;
public QuerydslPredicateProjectionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public QuerydslPredicateProjectionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager, EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver, null);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
public QuerydslPredicateProjectionRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager, EntityPathResolver resolver, CrudMethodMetadata metadata) {
super(entityInformation, entityManager, resolver, metadata);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
#Override
public <Projection> List<Projection> findAll(Predicate predicate, FactoryExpression<Projection> factoryExpression) {
return createQuery(predicate).select(factoryExpression).fetch();
}
#Override
public <Projection> List<Projection> findAll(Predicate predicate, Sort sort, FactoryExpression<Projection> factoryExpression) {
JPQLQuery<Projection> query = createQuery(predicate).select(factoryExpression);
querydsl.applySorting(sort, query);
return query.fetch();
}
#Override
public <Projection> Page<Projection> findAll(Predicate predicate, Pageable pageable, FactoryExpression<Projection> factoryExpression) {
JPQLQuery<Projection> query = createQuery(predicate).select(factoryExpression);
querydsl.applyPagination(pageable, query);
querydsl.applySorting(pageable.getSort(), query);
QueryResults<Projection> queryResults = query.fetchResults();
return new PageImpl<>(queryResults.getResults(), pageable, queryResults.getTotal());
}
}
5.Example entity
#Entity
public class Example extends Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
#Column
private String name;
#Column
private String surname;
#Column
private Integer year;
public Example() {
}
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getSurname() {return surname;}
public void setSurname(String surname) {this.surname= surname;}
public Integer getYear() {return year;}
public void setSurname(Integer year) {this.year= year;}
}
6.Example repository
#Repository
public interface ExampleRepository extends JpaRepository<Example, Long>, QuerydslPredicateProjectionRepository<Example> { }
7.Example usage
configuration :
#EnableJpaRepositories(repositoryFactoryBeanClass = QuerydslPredicateProjectionRepositoryFactoryBean.class)
typical usage :
//get list of entities only with year field value set - memory consuming
List<Example> years = repository.findAll(predicate, Projections.fields(Example.class, QExample.example.year));
//get list of tuples - looks nicer - less memory consuming
List<Tuple> years = repository.findAll(predicate, Projections.tuple(QExample.example.year));
//get list of integers - nice :)
List<Integer> years = repository.findAll(predicate, Projections.constructor(Integer.class, QExample.example.year));
1. CustomJpaRepositoryFactoryBean
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
public class CustomJpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends JpaRepositoryFactoryBean<T, S, ID> {
public CustomJpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new CustomJpaRepositoryFactory(entityManager);
}
}
2. CustomJpaRepositoryFactory
import static org.springframework.data.querydsl.QueryDslUtils.QUERY_DSL_PRESENT;
import javax.persistence.EntityManager;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
public class CustomJpaRepositoryFactory extends JpaRepositoryFactory {
public CustomJpaRepositoryFactory(EntityManager entityManager) {
super(entityManager);
}
#Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
if(QUERY_DSL_PRESENT) {
Class<?> repositoryInterface = metadata.getRepositoryInterface();
if(CustomQueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface)) {
return CustomQueryDslJpaRepository.class;
} else if(QueryDslPredicateExecutor.class.isAssignableFrom(repositoryInterface)) {
return QueryDslJpaRepository.class;
}
}
return SimpleJpaRepository.class;
}
}
3. CustomQueryDslJpaRepository
import java.io.Serializable;
import javax.persistence.EntityManager;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.QueryDslJpaRepository;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import com.querydsl.core.QueryResults;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
public class CustomQueryDslJpaRepository<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID> implements CustomQueryDslPredicateExecutor<T> {
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final Querydsl querydsl;
public CustomQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
this(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver resolver) {
super(entityInformation, entityManager, resolver);
EntityPath<T> path = resolver.createPath(entityInformation.getJavaType());
PathBuilder<T> builder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.querydsl = new Querydsl(entityManager, builder);
}
public <DTO> Page<DTO> findAll(Predicate predicate, Pageable pageable, FactoryExpression<DTO> factoryExpression) {
JPQLQuery<DTO> query = createQuery(predicate).select(factoryExpression);
querydsl.applyPagination(pageable, query);
querydsl.applySorting(pageable.getSort(), query);
QueryResults<DTO> queryResults = query.fetchResults();
return new PageImpl<>(queryResults.getResults(), pageable, queryResults.getTotal());
}
}
4. CustomQueryDslPredicateExecutor
import com.querydsl.core.types.FactoryExpression;
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;
public interface CustomQueryDslPredicateExecutor<T> extends QueryDslPredicateExecutor<T> {
<DTO> Page<DTO> findAll(Predicate predicate, Pageable pageable, FactoryExpression<DTO> factoryExpression);
}
5. example
#EnableJpaRepositories(
...
repositoryFactoryBeanClass = CustomJpaRepositoryFactoryBean.class
)
public interface ProductRepository extends JpaRepository<Product, Long> implements CustomQueryDslPredicateExecutor<Product> {
}