Spring Data MongoDB Repository with custom collection name - java

I am using Spring Data for MongoDB and I need to be able to configure collection at runtime.
My repository is defined as:
#Repository
public interface EventDataRepository extends MongoRepository<EventData, String> {
}
I tried this silly example:
#Document(collection = "${mongo.event.collection}")
public class EventData implements Serializable {
but mongo.event.collection did not resolve to a name as it does with a #Value annotation.
A bit more debugging and searching and I tried the following:
#Document(collection = "#{${mongo.event.collection}}")
This produced an exception:
Caused by: org.springframework.expression.spel.SpelParseException: EL1041E:(pos 1): After parsing a valid expression, there is still more data in the expression: 'lcurly({)'
at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:129)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:60)
at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:32)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpressions(TemplateAwareExpressionParser.java:154)
at org.springframework.expression.common.TemplateAwareExpressionParser.parseTemplate(TemplateAwareExpressionParser.java:85)
Perhaps I just don't know how to quite use SPel to access values from Spring's Property Configurer.
When stepping through the code, I see that there is a way to specify collection name or even expressions, however, I am not sure which annotation should be used for this purpose or how to do it.
Thanks.
-AP_

You can solve this problem by just using SPeL:
#Document(collection = "#{environment.getProperty('mongo.event.collection')}")
public class EventData implements Serializable {
...
}
Update Spring 5.x:
Since Spring 5.x or so you need an additional # before environment:
#Document(collection = "#{#environment.getProperty('mongo.event.collection')}")
public class EventData implements Serializable {
...
}
Docs:
SpEL: 4.2 Expressions in Bean Definitions
SpEL: 4.3.12 Bean References
PropertyResolver::getProperty

So, at the end, here is a work around that did the trick. I guess I really don't know how to access data from Spring Properties Configurer using the SPeL expressions.
In my #Configuration class:
#Value("${mongo.event.collection}")
private String
mongoEventCollectionName;
#Bean
public String mongoEventCollectionName() {
return
mongoEventCollectionName;
}
On my Document:
#Document(collection = "#{mongoEventCollectionName}")
This, appears to work and properly pick up the name configured in my .properties file, however, I am still not sure why I could not just access the value with $ as I do in the #Value annotation.

define your entity class like
#Document(collection = "${EventDataRepository.getCollectionName()}")
public class EventData implements Serializable {
Define a custom repository interface with getter and setter methods for "collectionName"
public interface EventDataRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
provide implementation class for custom repository with "collectionName" implementation
public class EventDataRepositoryImpl implements EventDataRepositoryCustom{
private static String collectionName = "myCollection";
#Override
public String getCollectionName() {
return collectionName;
}
#Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
Add EventDataRepositoryImpl to the extends list of your repository interface in this it would look like
#Repository
public interface EventDataRepository extends MongoRepository<EventData, String>, EventDataRepositoryImpl {
}
Now in your Service class where you are using the MongoRepository set the collection name, it would look like
#Autowired
EventDataRepository repository ;
repository.setCollectionName("collectionName");

Entity Class
#Document // remove the parameters from here
public class EscalationCase
{
}
Configuration class
public class MongoDBConfiguration {
private final Logger logger = LoggerFactory.getLogger(MongoDBConfiguration.class);
#Value("${sfdc.mongodb.collection}") //taking collection name from properties file
private String collectionName;
#Bean
public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory, MongoMappingContext context) {
MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory), context);
converter.setTypeMapper(new DefaultMongoTypeMapper(null));
MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory, converter);
if (!mongoTemplate.collectionExists(collectionName)) {
mongoTemplate.createCollection(collectionName); // adding the collection name here
}
return mongoTemplate;
}
}

Related

Env variables in data class in Redis model annotation

Troubling to specify dynamic env variable in the model class #Hashkey Redis annotation.
Model:
#RedisHash("${spring.redis.namespace}:Book")
public class Book {
#Id
private String id;
private String name;
}
My application.properties file:
spring.redis.namespace=local
The resulting key is "${spring.redis.namespace}:Book" instead of local:Book
Could anyone help me with this?
Please use Keyspaces to do it. There two ways. I use one way to finish your requirement.
#Configuration
#EnableRedisRepositories(keyspaceConfiguration = MyKeyspaceConfiguration.class)
public class ApplicationConfig {
#Value("${spring.redis.namespace}:Book")
String myKey;
//... RedisConnectionFactory and RedisTemplate Bean definitions omitted
public static class MyKeyspaceConfiguration extends KeyspaceConfiguration {
#Override
protected Iterable<KeyspaceSettings> initialConfiguration() {
return Collections.singleton(new KeyspaceSettings(Book.class, myKey));
}
}
}

Is it ok to initialize a #Component object as a private final property in a data class?

Is it ok to initialize a property as part of a data class like in the code below, when the property is defined as a #Component?
#SuperBuilder
#Data
public class DataClass{
private final RandomUUIDGenerator generator = new RandomUUIDGenerator();
#Builder.Default
String uuid = generator.generate();
}
The RandomUUIDGenerator is defined like this:
#Component
public class RandomUUIDGenerator implements UUIDGenerator {
public UUID generate() {
return UUID.randomUUID().toString();
}
}
You can do it, however it is not recommended as you are not utilising the benefits of dependency injection. You should autowire RandomUUIDGenerator using #Autowired annotation.

Spring Cache Evict does not work

Hi I have problem with clean cache when method is executed.
Here is my configuration and caches methods:
#Configuration
#EnableCaching
#AutoConfigureAfter(value = {MetricsConfiguration.class, DatabaseConfiguration.class})
#Profile("!" + Constants.SPRING_PROFILE_FAST)
public class CacheConfiguration {
private final Logger log = LoggerFactory.getLogger(CacheConfiguration.class);
public static final String STOCK_DETAILS_BY_TICKER_CACHE = "stockDetailsByTickerCache";
public static final String RSS_NEWS_BY_TYPE_CACHE = "rssNewsByTypeCache";
#Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>();
caches.add(new ConcurrentMapCache(STOCK_DETAILS_BY_TICKER_CACHE));
caches.add(new ConcurrentMapCache(RSS_NEWS_BY_TYPE_CACHE));
cacheManager.setCaches(caches);
return cacheManager;
}
}
This method i want to cache:
#Cacheable(cacheNames = CacheConfiguration.RSS_NEWS_BY_TYPE_CACHE, key = "#type")
public ResponseEntity<List<NewsDetailsDTO>> getLatestNewsMessageByType(RssType type) {
Pageable pageable = new PageRequest(0, 5, Sort.Direction.DESC, "createdDate");
List<NewsMessage> latestNewsMessage = newsMessageRepository.findByType(type, pageable).getContent();
return new ResponseEntity<List<NewsDetailsDTO>>(mapToDTO(latestNewsMessage), HttpStatus.OK);
}
On execution of this method i would like to clean cache by type:
#CacheEvict(cacheNames={CacheConfiguration.RSS_NEWS_BY_TYPE_CACHE}, beforeInvocation = true, key = "#news.type")
public void save(NewsMessage news) {
newsMessageRepository.save(news);
}
And NewsMessage object looks like:
#Entity
#Table(name = "NEWS_MESSAGE")
public class NewsMessage extends ChatMessage {
<other fileds>
#NotNull
#Enumerated(EnumType.STRING)
private RssType type;
}
The cache thing works fine, by the first time there is a query to DB by second and next the data is fetch from cache. Problem is when I update data the #CacheEvict does not clean the cache. I was trying to clean all cache using this annotation:
#CacheEvict(cacheNames={CacheConfiguration.RSS_NEWS_BY_TYPE_CACHE}, allEntries = true)
But it also does not work. Could you help me?
From where do you call the save() method?
In your own answer it looks like you have moved the annotations to another class/interface to invoke the proxy object of that class/interface (btw annotations should generally not be used in interfaces, because they often don't get catched with default configuration).
Therefore my question: do you know the spring aop proxy? You have to call annotated methods from methods outside your MessageRepository class to invoke the proxy object.
General documentation for that is here: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#aop-understanding-aop-proxies
or with examples here http://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring
I found the workaround for my problem. I had to moved the annotation upper to the spring data jpa interace.
public interface NewsMessageRepository extends JpaRepository<NewsMessage, Long> {
#CacheEvict(cacheNames = {CacheConfiguration.RSS_NEWS_BY_TYPE_CACHE}, beforeInvocation = true, key = "#p0.type")
NewsMessage save(NewsMessage news);
}
Now it is working as i expected, but still have no idea why it did not work in my service. Maybe because my services implements two interfaces?
#Service
public class NewsMessageService implements RssObserver, NewsMessageServiceable {
}
You need a public RssType getType() method in your NewsMessage class. The key expression "#news.type" in your #CacheEvict annotation is expecting either a public field named "type" or a public getter method named "getType".

Spring Boot extending CrudRepository

I'm using Hibernate in a Spring Boot app. I'm making a new CrudRepository for all my Model objects, to do basic CRUD tasks. They look like this:
#Repository
public interface FoobarCrudRepo extends CrudRepository<Foobar, Long> {
}
But then I always need to do some additional things, like custom search queries with inequalities and such. I follow a pattern like this:
#Repository
public class FoobarDao {
#PersistenceContext
EntityManager em;
public List<Foobar> findFoobarsByDate(Date date) {
String sql = "select fb from Foobar fb where createdDate > :date";
...
return query.getResultList();
}
}
My question is, can I combine these two concepts into a single class? I tried making it an abstract class, like so:
#Repository
public abstract class FoobarCrudRepo extends CrudRepository<Foobar, Long> {
#PersistenceContext
EntityManager em;
public List<Foobar> findFoobarsByDate(Date date) {
String sql = "select fb from Foobar fb where createdDate > :date";
...
return query.getResultList();
}
}
But then Spring didn't create a bean for it.
How can I accomplish this?
Thanks!
There are lots of ways you could probably accomplish this. If you really need absolute control try this
interface FoobarRepositoryCustom{
List<Foobar> findFoobarsByDate(Date date);
}
interface FoobarRepository extends CrudRepository<Foobar, Long>, FoobarRepositoryCustom
public class FoobarRespoitoryImpl implements FoobarRepositoryCustom{
#PersistenceContext private EntityManager em;
public List<Foobar> findFoobarsByDate(Date date) {
String sql = "select fb from Foobar fb where createdDate > :date";
...
return query.getResultList();
}
}
There is also the possibility to go a simpler route and the query can be auto generated for you based on the method name. In your example you could just add this to your FoobarCrudRepo and Spring should do the rest assuming Foobar has a property named CreatedDate
List<Foobar> findByCreatedDateGreaterThan(Date date);
For reference on how Spring can generate queries based on the method name see this http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods.query-creation
Completely new to Spring Data, but having searched a bit it is my impression that you do not have to leave the interface to create custom logic - rather you would create either an annotated interface method, an interface method that follows a special naming scheme or a default interface method with custom logic:
Screenshot from Baeldung: Introduction to Spring.
Here is a link to the documentation. Notice "table 4. Supported keywords inside method names" which can be used to create interface methods, whose name conveys information to the code generator about which query to create (See part of table below).
The problem here is abstract keyword.
#Repository
public abstract class FoobarCrudRepo extends CrudRepository<Foobar, Long>
Spring will not create a bean for a class unless it is a concrete class.
That's why you are getting a bean for it.
This is what worked for me...
#SpringBootApplication(scanBasePackages = { "com.myproject" })
#EnableJpaRepositories(basePackages="com.myproject.sprinbootapp.repository")
#EntityScan("com.myproject.sprinbootapp.model")
public class SpringbootAppWithDatabaseApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAppWithDatabaseApplication.class, args);
}
}
#Service
public class TopicService {
#Autowired
private TopicRepository topicRepository;
private List<Topics> topics = new ArrayList<Topics>();
public List<Topics> getAllTopics(){
List<Topics> listOfTopics = new ArrayList<Topics>();
topicRepository.findAll().forEach(listOfTopics::add);;
return listOfTopics;
}
}
#Entity
public class Topics {
#Id
private String id;
private String name;
public Topics(){
}
getters and setters...
}
public interface TopicRepository extends CrudRepository<Topics, String> {
}
we can use the JPA EntityManager for direct sql actions:
public interface VerificationsRepository extends
CrudRepository<Verification, Integer>,
DAOAccess
{ }
interface DAOAccess {
List findByEmail(String email);
}
class DAOAccessImpl implements DAOAccess {
#PersistenceContext private EntityManager em;
public List findByEmail(String email) {
String sql =
"select * from verifications where email = ?";
Query query = em.createNativeQuery(sql, Verification.class)
.setParameter(1, email);
return query.getResultList();
}
}

ManagedBeanCreationException

I have Users pojo and this pojo is not extend from T.It is like this
#Entity
#Table(name = "USERS")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "USERS.findAll", query = "SELECT s FROM USERS s")})
public class USERS implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected USERSPK usersPK;
#Lob
#Column(name = "Name")
private Stirng name;
#Size(max = 20)
#Column(name = "Surname")
private String surname;
And I wanted to select smth.from this table.Service and DAO classes are below:
public interface CommonService {
public List<Object> hepsiniGetir2(Class persistenceClass, String property, Object searchCrit);
}
This is implementation of interface;
#Service("commonService")
public class CommonServiceImpl implements CommonService, Serializable {
#Transactional
public List<Object> hepsiniGetir2(Class persistenceClass, String property, Object searchCrit) {
return commonDao.findAllByCrit2(persistenceClass, property, searchCrit);
}
}
And here dao interface:
public interface CommonDAO extends GenericDAO<TemelNesne, Long> {
public List<Object> findAllByCrit2(Class persistenceClass, String property, Object searchCrit);
}
Here implementation of dao class:
#Repository
public class CommonDAOImpl extends GenericDAOImpl<TemelNesne, Long> implements CommonDAO {
public List findAllByCrit2(Class persistenceClass, String property, Object searchCrit) {
Criteria c = sessionFactory.getCurrentSession().
createCriteria(persistenceClass).add(Restrictions.eq(property, searchCrit));
List<Object> list = c.list();
if (list.isEmpty()) {
return null;
} else {
return list;
}
}
}
In view class I call this method like ;
#ManagedBean(name="userView", eager=true)
#ViewScoped
public class UserView extends BaseView implements Serializable {
#ManagedProperty("#{commonService}")
private CommonService commonService;
private List<USERS> list;
#PostConstruct
public void init() {
list = (List) commonService.hepsiniGetir2(USERS.class, "name", "Deniz");
}
}
Lastly, I had this exception:
Tem 04, 2014 5:38:10 PM
com.sun.faces.application.view.FaceletViewHandlingStrategy
handleRenderException SEVERE: Error Rendering View[/userList.xhtml]
com.sun.faces.mgbean.ManagedBeanCreationException: An error occurred
performing resource injection on managed bean userView Caused by:
org.hibernate.type.SerializationException: could not deserialize
Caused by: java.io.StreamCorruptedException: invalid stream header:
3C3F786D
Your problem is a concept problem. JSF beans cannot autowire Spring beans, nor the other way around. This is because they are managed in different containers. You should integrate Spring with JSF to allow injection, which basically boils down to Spring controlling everything. There are plenty tutorials in the net about doing this. One good tutorial is mkyong's: JSF 2 + Spring 3 Integration Example. Basically, these are the steps (taken from his tutorial):
In faces-config.xml file, add Expression Language (EL) resolver:
<application>
<el-resolver>
org.springframework.web.jsf.el.SpringBeanFacesELResolver
</el-resolver>
</application>
Instead using JSF annotations, use Spring annotations for JSF managed beans. Example applied to your classes:
#Component("userView")
#Scope("view")
public class UserView extends BaseView implements Serializable {
#Autowired
private CommonService commonService;
private List<USERS> list;
#PostConstruct
public void init() {
list = (List) commonService.hepsiniGetir2(Sertifikalar.class, "name", "Deniz");
}
}
Note that in this example, I'm using #Scope("view") but Spring doesn't have a view scope by default, the team is still working on it. You have to implement this scope manually. Fortunately, you can use Cagatay's implementation to solve this.
Apart of these problems, you have another conceptual problem: the only bean that supports eager=true is #ApplicationScoped since it will work as a #Singleton Spring bean, other managed beans will ignore this attribute at all.

Categories

Resources