I am currently working on a project where I have created the following custom Repository:
public interface ServiceRepository<T extends ServiceEntity> extends JpaRepository<T, UUID>, ServiceRepositoryCustom {
}
public interface ServiceRepositoryCustom {
List<ServiceEntity> findAllContainingName(String query);
}
#Repository("Repo")
public class ServiceRepositoryCustomImpl implements ServiceRepositoryCustom {
private final EntityManager em;
public ServiceRepositoryCustomImpl(EntityManager em) {
System.out.println("I got constructed");
this.em = em;
}
#Override
public List<ServiceEntity> findAllContainingName(String name) {
System.out.println("I got called with: " + name);
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ServiceEntity> cq = cb.createQuery(ServiceEntity.class);
Root<ServiceEntity> serviceEntity = cq.from(ServiceEntity.class);
List<Predicate> predicates = new ArrayList<>();
if(name != null) {
// predicates.add(cb.equal(serviceEntity.get("name"), name));
predicates.add(cb.like(serviceEntity.get("name"), name + "%"));
}
cq.where(predicates.toArray(predicates.toArray(new Predicate[0])));
return em.createQuery(cq).getResultList();
}
}
The print statement "I got called with: " never gets called. So for whatever reason Spring Boot is not running the method through my custom implementation.
Any suggestions? Any help is much appreciated
Edit:
Here is the code that injects and uses the Repository in question
#Repository
public interface PineappleServiceRepository extends ServiceRepository<PineappleServiceEntity> {
}
#Component("Registry")
#DependsOn({"Context", "Repo"})
public class Registry {
private final List<ServiceRepository<? extends ServiceEntity>> serviceRepositories = new ArrayList<>();
public Registry(PineappleServiceRepository pineappleServiceRepository) {
this.serviceRepositories.add(pineappleServiceRepository);
}
}
Edit 2:
The code prints "I got constructed"
Edit 3:
Class where findAllContainingName is called
#RestController
#RequestMapping("/test")
#DependsOn("Registry")
public class ServiceController {
private final Registry registry;
public ServiceController(#NotNull Registry registry) {
this.registry = registry;
}
#GetMapping("")
List<ServiceEntity> all(#RequestParam("q") String query) {
return getAllServices(query);
}
private #NotNull List<ServiceEntity> getAllServices(String query) {
List<ServiceEntity> response = new ArrayList<>();
for(ServiceRepository<? extends ServiceEntity> repo: this.registry.getServiceRepositories()){
response.addAll(repo.findAllContainingName(query));
}
return response;
}
}
Edit 4:
Here the entities:
#Entity
#Table(name = "services")
public abstract class ServiceEntity {
protected #Id
UUID id = UUID.randomUUID();
protected String name;
// Constructor + Getters and Setters
}
#Entity
public class PineappleServiceEntity extends ServiceEntity {
// Additional Properties, matching Constructors, Getters and Setters
}
So I was able to reproduce your problem and fix it. Issue with your code is that your PineappleServiceRepository is not extending ServiceRepositoryCustom directly. It seems your repository needs to implement it directly if you are accessing custom repository methods from that repository. I got that idea from this post.
So to fix your issue, either remove PineappleServiceRepository(as you don't have any properties in PineappleEntity) and use ServiceRepository to call that custom method or make PineappleServiceRepository extend ServiceRepositoryCustom.
I have pushed changes to GitHub with fix. You can take a look. If you want to keep PineappleServiceRepository and access custom method using this repository, let me know, I can update code.
Related
I am working spring boot project that uses spring data as an abstraction to access the database[MongoDB]. I want to change the write concern only for two specific operations.
Below is the entity and repository class that I use to access the Mongo DB collection:
Entity
#Document(collection = "tests")
#Data
#NoArgsConstructor
public class Test {
#Id
private String id;
private String name;
private String category;
}
Repository
#Repository
public interface TestRepository extends BaseMongoRepository<Test> {
...
#DeleteQuery(value="{'id':?0}, { writeConcern: { w : '2', wtimeout : 1000 }, delete=true")
void safeDeleteByTestId(String id,String name);
default void updateNameForAll(String category) {
final Query query = query(where("category").is(category);
final Update update = Update.update("name", name);
getMongoOperations().updateMulti(query, updategetMetadata().getCollectionName());
}
...
}
How can I modify the updateNameForAll method to increase the write concern only for this query? I don't want to override write concern for the entire collection or database.
Kind Regards,
Rando.
I found a workaround to this issue:
I created a new interface named ETestRepository like below:
public interface ETestRepository {
void safeUpdateNameForAll(String category);
}
Then I created a implementation of the interface:
public interface ETestRepositoryImpl implements ETestRepository {
#Autowired
private MongoTemplate mongoTemplate;
#Override
public void safeUpdateNameForAll(String accountId, String contextId, ChangeSetRowAction action) {
mongoTemplate.setWriteConcern(WriteConcern.W2);
final Query query = query(where("category").is(category);
final Update update = Update.update("name", name);
mongoTemplate.updateMulti(query,update, Test.class);
}
}
In the end, I modified the TestRepository interface to extend the ETestRepository interface to include the safeUpdateNameForAll method.
I have a project in spring boot and I'm using CrudRepository, but when I try to update, it doesn't do anything.
#Entity
public class PfmSelection implements Serializable{
#Id
private Integer releaseId;
private String preparedBy;
}
Repositiry
#Repository
public interface IPfmSelectionDao extends CrudRepository<PfmSelection, Integer> {
}
Service
public interface IPfmSelectionService {
public PfmSelection save(PfmSelection pfmSelection);
public PfmSelection findById(Integer id);
}
Service Impl
#Service
public class PfmSelectionService implements IPfmSelectionService {
#Autowired
private IPfmSelectionDao pfmSelectionDao;
#Override
#Transactional
public PfmSelection save(PfmSelection pfmSelection) {
return this.pfmSelectionDao.save(pfmSelection);
}
#Override
#Transactional(readOnly = true)
public PfmSelection findById(Integer id) {
return this.pfmSelectionDao.findById(id).orElse(null);
}
}
Service where I use the other Service
#Autowired
private IPfmSelectionService pfmSelectionService;
private void updatePfm(PushModel pushModel) {
PfmSelection pfm = this.pfmSelectionService.findById(167427);
pfm.setPreparedBy("Rodrige");
pfmSelectionService.save(pfm);
}
I don't receive any error in the console.
You need to take a few steps to know what the problem is
Take the return of pfmSelectionService.save(pfm) and print the saved instance returned like below:
private void updatePfm(PushModel pushModel) {
PfmSelection pfm = this.pfmSelectionService.findById(167427);
pfm.setPreparedBy("Rodrige");
PfmSelection pfm2 = pfmSelectionService.save(pfm);
System.out.println(pfm2.getPreparedBy());
}
Put logger/debugger inside the save method, before and after the save method and check for the entry/exit sop/logger statements in log/console like
#Override
#Transactional
public PfmSelection save(PfmSelection pfmSelection) {
System.out.println("Inside save method");
PfmSelection pfmSelectionSaved =
this.pfmSelectionDao.save(pfmSelection);
System.out.println("Exits save method");
return pfmSelectionSaved;
}
Check for any Aop around advice or any place where the exception is being caught but eaten/not thrown further.
Check if there is any update query fired in the logs at the time of save call.
Also check if the setter method pfm.setPreparedBy("Rodrige"); is Empty?
I'm new to Java and I must make some DAO for my app. However, I don't want to make a DAO for each class (with interface) et override methods.
Is it possible to make a DAO extended by all the others, with methods working with all kind of Class ?
For example, a DAO that could handle class MyClass and class Foo with a single mehtod getList().
Thank you !
Not that gooed idea, in general, but...
If it is about low-level JDBC (no framework like Hibernate, Spring, etc.), then:
You can make an AbstractDAO class, then your other DAO-classes (UserDAO, ProductDAO, etc.), then you can make a CommonService class that has all those DAO-classes and provides the functions you need.
Example:
abstract class AbstractDAO {
private DataSource dataSource;
protected getDataSource() { // Inject it or hard-coded dataSource
return dataSource;
}
}
public class UserDAO extends AbstractDAO {
public User read(long id) {
// blablabla
return user;
}
public List<User> findAll() {
// blablabla
return users;
}
// and so on...
}
public class ProductDAO extends AbstractDAO {
public Product read(long id) {
// blablabla
return product;
}
public List<Product> findAll() {
// blablabla
return products;
}
// and so on...
}
Then other repositories, and then:
public class CommonService {
private final UserDAO userDAO = new UserDAO();
private final ProductDAO productDAO = new ProductDAO();
// other repositories
public User readUser(long id) {
return userDAO.read(id);
}
public Product readProduct(long id) {
return productDAO.read(id);
}
public List<User> findAllUsers() {
return userDAO.findAll();
}
public List<Product> findAllProducts() {
return productDAO.findAll();
}
}
And if you mean you want to make a generic repository (DAO), again not that good idea, because Spring has already made it in a quite good way (it calls JpaRepository, e.g. interface MyRepository extends JpaRepository<User, Long> { }):
Source: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.multiple-modules
But if you want, you can make such a mechanism too, based on something like this (but it will be a cumbersome work to make it work like it does in Spring, for instance; because they are a team of experts who worked day and night to realize such a tremendous project):
public abstract class Repo<T, K> {
public abstract T read(K id);
public abstract List<T> findAll();
}
or
public interface Repo<T, K> {
T read(K id);
List<T> findAll();
}
I have a collection called Products in my MongoDB database, which is represented by the interface IProductPrice in my Java code. The following repository declaration causes Spring Date to look to the collection db.collection: Intelliprice.iProductPrice.
I want it to configure it to look in db.collection: Intelliprice.Products using an external configuration rather than putting an #Collection(..) annotation on IProductPrice. Is this possible? How can I do this?
public interface ProductsRepository extends
MongoRepository<IProductPrice, String> {
}
The only way you can currently achieve this is by annotating your domain class with #Document using the collection property to define the name of the collection instances of this class shall be persisted to.
However, there's a JIRA issue open that suggests adding a pluggable naming strategy to configure the ways class, collection and property names are handled in a more global way. Feel free to comment your use case and vote it up.
using answer from Oliver Gierke above,
working on a project where I need to create multiple collections for one entity, I wanted to use the spring repositories and needed to specify the entity to use before using the repository.
I managed to modify the repository collection name on demand using this system, it using SPeL. You can only work on 1 collection at a time though.
Domain object
#Document(collection = "#{personRepository.getCollectionName()}")
public class Person{}
Default Spring Repository:
public interface PersonRepository
extends MongoRepository<Person, String>, PersonRepositoryCustom{
}
Custom Repository Interface:
public interface PersonRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
implementation:
public class PersonRepositoryImpl implements PersonRepositoryCustom {
private static String collectionName = "Person";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
To use it:
#Autowired
PersonRepository personRepository;
public void testRetrievePeopleFrom2SeparateCollectionsWithSpringRepo(){
List<Person> people = new ArrayList<>();
personRepository.setCollectionName("collectionA");
people.addAll(personRepository.findAll());
personDocumentRepository.setCollectionName("collectionB");
people.addAll(personRepository.findAll());
Assert.assertEquals(4, people.size());
}
Otherwise if you need to use configuration variables, you could maybe use something like this? source
#Value("#{systemProperties['pop3.port'] ?: 25}")
A little late,
but I've found you can set the mongo collection name dynamically in spring-boot accessing the application configuration directly.
#Document(collection = "#{#environment.getProperty('configuration.property.key')}")
public class DomainModel {...}
I suspect you can set any annotation attribute this way.
The only comment I can add is that you have to add # prefix to the bean name:
collection = "#{#beanName.method()}"
for the bean factory to inject the bean:
#Document(collection = "#{#configRepositoryCustom.getCollectionName()}")
public class Config {
}
I struggled to figure it out..
COMPLETE EXAMPLE:
#Document(collection = "#{#configRepositoryCustom.getCollectionName()}")
public class Config implements Serializable {
#Id
private String uuid;
private String profile;
private String domain;
private String label;
private Map<String, Object> data;
// get/set
}
public interface ConfigRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
#Component("configRepositoryCustom")
public class ConfigRepositoryCustomImpl implements ConfigRepositoryCustom {
private static String collectionName = "config";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
#Repository("configurations")
public interface ConfigurationRepository extends MongoRepository<Config, String>, ConfigRepositoryCustom {
public Optional<Config> findOneByUuid(String Uuid);
public Optional<Config> findOneByProfileAndDomain(String profile, String domain);
}
usage in serviceImpl:
#Service
public class ConfigrationServiceImpl implements ConfigrationService {
#Autowired
private ConfigRepositoryCustom configRepositoryCustom;
#Override
public Config create(Config configuration) {
configRepositoryCustom.setCollectionName( configuration.getDomain() ); // set the collection name that comes in my example in class member 'domain'
Config configDB = configurationRepository.save(configuration);
return configDB;
}
I use static class and method in SpEL;
public class CollectionNameHolder {
private static final ThreadLocal<String> collectionNameThreadLocal = new ThreadLocal<>();
public static String get(){
String collectionName = collectionNameThreadLocal.get();
if(collectionName == null){
collectionName = DataCenterApiConstant.APP_WECHAT_DOCTOR_PATIENT_COLLECTION_NAME;
collectionNameThreadLocal.set(collectionName);
}
return collectionName;
}
public static void set(String collectionName){
collectionNameThreadLocal.set(collectionName);
}
public static void reset(){
collectionNameThreadLocal.remove();
}
}
In Entity class ,#Document(collection = "#{T(com.test.data.CollectionNameHolder).get()}")
And then ,use
CollectionNameHolder.set("testx_"+pageNum)
in Service , and
CollectionNameHolder.reset();
Hope it helps you.
I'm trying to write simple DAO that will create entity objects based on their type stored in String field. How to return type that is changed dynamicly?
Method findById() of UserDAO class should return User class object. Same method for ProductDAO should return Product.
I don't want to implement findById in every class that extends DAO, it should be done automatically.
Example code:
class DAO {
protected String entityClass = "";
public (???) findById(int id) {
// some DB query
return (???)EntityFromDatabase; // how to do this?
}
}
class UserDAO extends DAO {
protected String entityClass = "User";
}
class ProductDAO extends DAO {
protected String entityClass = "Product";
}
class User extends Entity {
public int id;
public String name;
}
Modify it to
class DAO<T> {
// protected String entityClass = "";
public T findById(int id) {
return (T)EntityFromDatabase; // how to do this?
}
}
class UserDAO extends DAO<User> {
//protected String entityClass = "User";
}
class ProductDAO extends DAO<Product> {
//protected String entityClass = "Product";
}
class User extends Entity {
public int id;
public String name;
}
Use Generics in java. Find an example here.
public interface GenericDAO<T,PK extends Serializable> {
PK create(T entity);
T read(PK id);
void update(T entity);
void delete(T entity);
}
public class GenericDAOImpl<T,PK extends Serializable> implements GenericDAO<T,PK>{
private Class<T> entityType;
public GenericDAOImpl(Class<T> entityType){
this.entityType = entityType;
}
//Other impl methods here...
}
Firstly, instead of using the String, use the class. Next up, use an entityManager (see docs)
class DAO<T> {
private Class<T> entityClass;
// How you get one of these depends on the framework.
private EntityManager entityManager;
public T findById(int id) {
return em.find(entityClass, id);
}
}
Now you can use a different DAO dependent on the type e.g.
DAO<User> userDAO = new DAO<User>();
DAO<Product> userDAO = new DAO<Product>();
I strongly recommend you this article, Don't Repeat the DAO. And I must say, you are not having a bad idea.