JpaRepository mistakes custom interface as property - spring

For general spring JpaRepository DAO (no spring-boot) , if the interface extends a custom interface , spring will mistake the interface methods as object's properties .
For example
interface ILocalDateTime {
fun getLocalDateTime() : LocalDateTime
}
interface UserDaoCustom : ILocalDateTime {
// query functions skipped
}
interface UserDao : JpaRepository<User, Long>, UserDaoCustom
class UserImpl : UserDaoCustom {
#PersistenceContext
private lateinit var em: EntityManager
override fun getLocalDateTime(): LocalDateTime {
return LocalDateTime.now()
}
// query functions skipped
}
This is a simplified UserDao. UserDaoCustom extends ILocalDateTime which contains a method getLocalDateTime .
Note : localDateTime is not a field of User.
At runtime, JpaRepository will mistake getLocalDateTime (or localDateTime ?) as User's field , and throws such exception :
Caused by: org.springframework.data.repository.query.QueryCreationException:
Could not create query for public abstract java.time.LocalDateTime foo.ILocalDateTime.getLocalDateTime()!
Reason: Failed to create query for method public abstract java.time.LocalDateTime foo.ILocalDateTime.getLocalDateTime()!
No property getLocalDateTime found for type User!;
nested exception is java.lang.IllegalArgumentException:
Failed to create query for method public abstract java.time.LocalDateTime foo.ILocalDateTime.getLocalDateTime()!
No property getLocalDateTime found for type User!
Environment :
Kotlin 1.6.20
Spring 5.3.19
spring-data-jpa 2.5.11
How to solve this problem ? (with able or unable to modify ILocalDateTime's code)
Thanks.

I think it is about naming and how Spring pick up implementations of repository extensions.
TLDR;
Change name of your implementation from UserImpl to UserDaoCustomImpl.
I have checked a similar setup and using your naming fails with the exact same error, but naming it "right" makes it work as expected
public interface ILocalDateTime {
LocalDateTime getLocalDateTime();
}
#Repository
public interface UserDao extends UserDaoCustom, JpaRepository<User, Long> {
}
public interface UserDaoCustom extends ILocalDateTime{
}
#Repository
public class UserDaoCustomImpl implements UserDaoCustom {
#Override
public LocalDateTime getLocalDateTime() {
return LocalDateTime.now();
}
}
and tests
#ExtendWith(SpringExtension.class)
#DataJpaTest
class UserRepositoryTest {
#Autowired
private UserDao userDao;
#Test
public void savesUser() {
userDao.save(new User());
}
#Test
public void bazzinga() {
assert userDao.getLocalDateTime() != null;
System.out.println("Bazzinga!"+userDao.getLocalDateTime());
}
}
yelds

Related

required a bean of type 'java.lang.String' that could not be found

This the error iam when I tried start my spring boot application getting on console Action:
Consider defining a bean of type
'java.lang.String' in your
configuration.
My code
#Repository
public class ProductRepository {
#Autowired
JdbcTemplate jdbcTemplate;
#Bean
public void addProduct(String name) {
String sql = "INSERT INTO product VALUES (NULL, ?)";
jdbcTemplate.update(sql, name);
}
}
#Service
public class ProductService {
#Autowired
ProductRepository productRepository;
public void addProduct(String name) {
productRepository.addProduct(name);
}
}
#RestController
#RequestMapping(path="product")
public class ProductController {
#Autowired
ProductService productService;
#PostMapping(path="/add/{name}")
public void addProduct(#PathVariable
String name) {
productService.addProduct(name);
}
}
Here, you are trying to create bean with dependency object of type String in addProduct(). When Spring application starts, it tries to look for Bean of type String. It makes no sense.
So, no need of the #Bean annotation. The beans are usually created in classes annotated with #Configuration.
try removing #Bean over addproduct method

Create an instance of #EnableJpaRepositories repository for parameter

What should I pass as parameter to invoke Access.logLastName(?)? It's not clear to me how to put an instance of the implementation of UserRepository
com.company.app.persistence.test.Access
#Configuration
#EnableJpaRepositories({ "com.company.app.persistence.repository" })
public class Access {
static void logLastName( UserRepository repository ) {
for (User white: repository.findByLastName( "White" )) {
log.info( white.toString());
}
}
}
com.company.app.persistence.repository.UserRepository
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByLastName(String lastName);
}
Reference:
Inside the #SpringBootApplication annotated class of Getting Started Accessing Data with JPA, they have a method like
#Bean
public CommandLineRunner demo(UserRepository repository) {
return ( args ) -> {
repository.findByLastName( "White" );
};
}
it's not clear to me how the instance of UserRepository implementation is pass as the parameter.
From #dunni's comment:
Make sure com.company.app.persistence.test.Access.java is a spring
bean
It has #Configuration, which makes it qualify as a spring bean I think(?)
then in the class where I want to invoke logLastName(), you can simply #Autowired the interface:
class Invoker {
#Autowired UserRepository userRepo;
void invoke() {
Access.logLastName( userRepo );
}
}

Best practise when using Querydsl with Spring Data

Using Spring Data nad Querydsl we can just declare repository interface and skip the implementation class. Some methods with a specific name or using #Query annotation and that's all.
But sometimes I'd like to use JPAQuery and define method's body by myself, let's say
#Repository
public class MyRepositoryImpl implements MyRepository {
#PersistenceContext
private EntityManager em;
#Override
public List<Tuple> someMethod(String arg) {
JPAQuery query = new JPAQuery(em);
...
}
but this way I would have to implement other MyRepository interface methods, which ruins all Spring Data's advantages!
I can see two options:
Declare another interface per each repository and then normally implement it (which doubles number of interfaces)
Inject EntityManager into #Service class and implement my custom methods there
I like option #2 more, but as far I as know, in #Service class we should only call repository methods, so it's not a perfect solution as well.
So how does programmers deal with it?
You should not implement the actual Spring Data repository, instead you have to declare another custom interface where you can put your custom methods.
Let's say you have a MyRepository, defined as
#Repository
public interface MyRepository extends JpaRepository<Tuple, Long> {}
Now you want to add your custom findTuplesByMyArg(), for a sake of purpose you need to create custom repository interface
public interface MyRepositoryCustom {
List<Tuple> findTuplesByMyArg(String myArg);
}
Afterwards comes the implementation of custom interface
public class MyRepositoryImpl implements MyRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Tuple> findTuplesByMyArg(String myArg) {
JPAQuery query = new JPAQuery(em);
...
}
}
And we need to change MyRepository declaration, so it extends custom repository, so that
#Repository
public interface MyRepository extends JpaRepository<Tuple, Long>, MyRepositoryCustom {}
And you can easily access your findTuplesByMyArg() by injecting MyRepository, e.g.
#Service
public class MyService {
#Autowired
private MyRepository myRepository;
public List<Tuple> retrieveTuples(String myArg) {
return myRepository.findTuplesByMyArg(myArg);
}
}
Pay attention that names are important here (you need to have Impl postfix by default configs in repo implementation).
You can find all needed information here
I would suggest a minor rectification to the answer above, which tries to use JPAQueryFactory. It is good to make use of the provided factory class.
public class MyRepositoryImpl implements MyRepositoryCustom {
#Autowired
private JPAQueryFactory factory;
#Override
public List<Tuple> findTuplesByMyArg(String myArg) {
JPAQuery query = factory.query();
...
}}
#Configuration
public class Config {
#Autowired
private EntityManager em;
#Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}

Spring data jpa repository property not found

I am new to Spring Data JPA. I've tried to create a custom method for the repository but it does throw an exception. Here is my current implementation:
public interface EmployeeRepository extends
CrudRepository<Employee, Long>,EmployeeRepositoryCustom {
}
#Repository
public interface EmployeeRepositoryCustom {
public void updateEmployee(String field, String value,long id);
}
public class EmployeeRepositoryImpl implements EmployeeRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
#Override
public void updateEmployee(String field, String value, long id) {
// Implementation goes here
}
}
And this is the exception that occurs when I start the application (I am using Spring boot).
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property updateEmployee found for type Employee!
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:75)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:327)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:307)
Since you have tagged spring-boot, I assume you are using spring-boot. If so, you can try following method.
#Repository
public interface EmployeeRepositoryCustom {
#Modifying
#Query(value = "update Employee query", nativeQuery = true)
public void updateEmployee(String field, String value,long id);
}
You do not need to have an implementation and you can have named parameters in query above. Spring-boot will handle rest.

Spring 4 bean autowiring with generics

I am using Spring 4 via Spring Boot 1.1.8 and have created a class to cache some data. The class relies on generics to work but I'm having trouble with Spring and autowiring this class as a bean in another service.
I get errors like this:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [orm.repository.BaseRepository] is defined: expected single matching bean but found 2: dataTypeRepository,propertyNameRepository
The class in question:
/**
* The purpose of this class is to get data from a cache backed by a database
* and if it does not exist to create it and insert into the database.
*/
#Service
public class CacheByName<TRepo extends BaseRepository, TItem extends BaseWithName> {
private final TRepo repo;
private final Class<TItem> itemClass;
private final Map<String, TItem> itemsCache; // TODO: change to better caching strategy
#Autowired
public CacheByName(TRepo repo, Class<TItem> itemClass) {
this.repo = repo;
this.itemClass = itemClass;
itemsCache = new HashMap();
}
public TItem getCreateItem(String name) {
TItem item = null;
if(itemsCache.containsKey(name)) {
item = itemsCache.get(name);
} else {
// try and load from db
item = (TItem) repo.findByName(name);
if(item == null) {
try {
item = itemClass.newInstance();
item.setName(name);
repo.saveAndFlush(item);
} catch (InstantiationException | IllegalAccessException ex) {
// TODO: log and handle better
return null;
}
}
itemsCache.put(name, item);
}
return item;
}
}
The class BaseRepository extends JpaRepository as follows. Other actual repositories extend this one.
#NoRepositoryBean
public interface BaseRepository<T extends Object, ID extends Serializable> extends JpaRepository<T, ID> {
public T findByName(String name);
}
The class BaseWithName is a MappedSuperclass that defines a name property and getters/setters for it. Other more concrete entity classes extend this.
I am trying to inject the CacheByName class into another service like the following. Note that I am defining the actual repository and entity class as generics in the constructor:
#Service
public class DataImporter extends BaseImporter {
private static final Logger log = LoggerFactory.getLogger(PropertyImporter.class);
private final PropertyNameRepository propertyNameRepo;
private final CacheByName<DataTypeRepository, DataType> dataTypeCache;
#Autowired
public PropertyImporter(RestTemplate restTemplateD5,
CacheByName<DataTypeRepository, DataType> dataTypeCache) {
super(restTemplateD5);
this.dataTypeCache = dataTypeCache;
}
.
.
.
}
My AppConfig.java looks like the following:
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class AppConfig {
#Value("${username}")
private String username;
#Value("${password}")
private String password;
#Bean
public RestTemplate restTemplateD5() {
return RestTemplateFactory.createWithHttpBasicAuth(username, password);
}
}
I haven't been able to find much information about creating beans that use generics. I suspect I need to create another #Bean definition in my AppConfig but I wasn't able to implement anything that worked.
As BaseRepository is also a generic type, I think you missed to add the generic type there. That should help Spring to find a proper bean to inject:
public class CacheByName<TRepo extends BaseRepository<TItem, ? extends Serializable>, TItem extends BaseWithName>
This should also make the cast no longer needed:
item = repo.findByName(name);

Resources