I've got next method in UserService:
#Cacheable(value = "user", key="#p0")
public User find(String user) {
return userRepository.findByUser(User);
}
It caches well. In other service I have:
#Transactional
public void updateToken(int id, String token) {
Group group = groupRepository.findOne(id);
group.getMembers().forEach(member -> {
member.getUser().setToken(token);
removeUserCacheByName(member.getUser().getName());
});
groupRepository.save(group);
}
#CacheEvict(value = "user", key="#p0")
public void removeUserCacheByName(String name) {
log.debug("Removing user cache by name {}.", name);
}
After updateToken method, cache does not clear.
What you're seeing is normal. You're calling removeUserCacheByName() from within the Proxy object so the catching aspect doesn't execute. You have this behaviour explained in the documentation.
You can do some things to work around this:
1) Take the evict method (removeUserCacheByName) to another bean, autowire it in updateToken()'s class, and call it.
2) An ugly but useful one, autowire the ApplicationContext, get the same object from it and call the method, e.g.:
public class UserService{
#Autowired
private ApplicationContext ac;
#Transactional
public void updateToken(int id, String token) {
Group group = groupRepository.findOne(id);
group.getMembers().forEach(member -> {
member.getUser().setToken(token);
UserService sameBean = ac.getBean(UserService.class);
sameBean.removeUserCacheByName(member.getUser().getName());
});
groupRepository.save(group);
}
#CacheEvict(value = "user", key="#p0")
public void removeUserCacheByName(String name) {
log.debug("Removing user cache by name {}.", name);
}
}
Related
I'm trying to use Spring Cache to store data, generated by another method inside Service class.
This method marked with #Cacheable is a public method, the cache is being called in Controller layer.
When I do debugging, I inspect the object cacheManager, I found that it contains the map that I stored, but when calling the method cacheManager.getCache("cache") it return null.
Question is why that method return null while the object is holding the value?
This is the config, service and controller:
Spring bean config:
#EnableCaching
public class CachingConfig {
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("optCache");
}
}
Service:
public void verify(Request request, String authorization) {
String memberId = parseAuthToken(authorization).getMembershipID();
buildOtpCache(memberId, request.getTokenUUID(), 0)
}
#Cacheable("otpCache")
public OTPCache buildOtpCache(String memberId, String uuid, int counter) {
return OTPCache.builder()
.memberId(memberId)
.tokenUUID(uuid)
.timestamp(LocalDateTime.now())
.counter(counter)
.build();
}
Controller:
#Override
public void verifyOTP(MeOTPVerifyRequest verifyOTPRequest, String authorization) {
String memberId = parseAuthToken(authorization).getMembershipID();
Collection<String> a = cacheManager.getCacheNames();
OTPCache OTPCache = cacheManager.getCache("cache").get(memberId, OTPCache.class);
otpService.verify(verifyOTPRequest, authorization);
}
EDIT:
This is my new service class, remove #Cacheable annotation:
public void verify(Request request, String authorization) {
String memberId = parseAuthToken(authorization).getMembershipID();
cacheManager.getCache("optCache").put(memberId, buildOtpCache(memberId, request.getTokenUUID(), 0));
}
public OTPCache buildOtpCache(String memberId, String uuid, int counter) {
return OTPCache.builder()
.memberId(memberId)
.tokenUUID(uuid)
.timestamp(LocalDateTime.now())
.counter(counter)
.build();
}
Assuming I've the following endpoints in spring boot
GET /todo
DELETE /todo/{id}
How can ensure that only entries for the userid are returned and that the user can only update his own todos?
I've a populated Authentication object.
Is there any build in way I can use? Or just make sure to always call findXyzByIdAndUserId where userid is always retrieved from the Principal?
I'm a bit worried about the possibility to forget the check and displaying entries from other users.
My approach to this would be a 3 way implementation: (using jpa & hibernate)
a user request context
a mapped superclass to get your context
a statement inspector to inject your userid
For example:
public final class UserRequestContext {
public static String getUserId() {
// code to retrieve your userid and throw when there is none!
if (userId == null) throw new IllegalStateException("userid null");
return userId;
}
}
#MappedSuperclass
public class UserResolver {
public static final String USER_RESOLVER = "USER_RESOLVER";
#Access(AccessType.PROPERTY)
public String getUserId() {
return UserRequestContext.getUserId();
}
}
#Component
public class UserInspector implements StatementInspector {
#Override
public String inspect(String statement) {
if (statement.contains(UserResolver.USER_RESOLVER)) {
statement = statement.replace(UserResolver.USER_RESOLVER, "userId = '" + UserRequestContext.getUserId() + "'" );
}
return sql;
}
#Bean
public HibernatePropertyCustomizer hibernatePropertyCustomizer() {
return hibernateProperies -> hibernateProperties.put("hibernate.session_factory.statement_inspector",
UserInspector.class.getName());
}
}
So your Entity looks like this:
#Entity
...
#Where(clause = UserResolver.USER_RESOLVER)
public class Todo extends UserResolver {
...
}
I've created this bean in order to get a Supplier<String>:
#Bean
public Supplier<String> auditIdSupplier() {
return () -> String.join(
"-",
"KEY",
UUID.randomUUID().toString()
);
}
As you can see, it's intented to only generate an straightforward identifier string.
Each time, it's called, a new identifier is supplied.
I'd like to change this behavior, in order to get the same generated identifier inside request scope. I mean, first time a request is reached, a new indentifier is generated. From then on, next calls no this Supplier has to return the first generated indentifier inside request scope.
Any ideas?
As it was written in commentary, maybe something like below will work:
#Bean
#RequestScope
public Supplier<String> auditIdSupplier() {
String val = String.join("-","KEY",UUID.randomUUID().toString());
return () -> val;
}
This is my version:
#Component
#Scope(WebApplicationContext.SCOPE_REQUEST)
public class AuditIdPerRequest {
private String key;
#PostConstruct
public void calculateKey() {
this.key = String.join(
"-",
"KEY",
UUID.randomUUID().toString()
);
}
public String getAuditId() {
return this.key;
}
}
You need to configure a request scoped bean
#Configuration
public class MyConfig {
#Bean
#RequestScope
public String myRequestScopedIdentifyer(NativeWebRequest httpRequest) {
// You don't need request as parameter here, but you can inject it this way if you need request context
return String.join(
"-",
"KEY",
UUID.randomUUID().toString());
}
And then inject it where appropriate with either field injection
#Component
public class MyClass {
#Autowired
#Qualifier("myRequestScopedIdentifyer")
private String identifier
or object factory
#Component
public class MyClass {
public MyClass(#Qualifier("myRequestScopedIdentifyer") ObjectFactory<String> identifyerProvider) {
this.identifyerProvider= identifyerProvider;
}
private final ObjectFactory<String> identifyerProvider;
public void someMethod() {
String requestScopedId = identifyerProvider.getObject();
}
The following works (results in the evict being performed):
fooController {
#ApiEndpoint
public delete(id) {
fooService.deleteFoo(id)
}
}
fooService {
#CacheEvict(value = "cache1", key = "#id")
public void deleteFoo(Long id) {
//delete logic here
}
}
But this does not work (nothing is evicted from the cache):
fooController {
#ApiEndpoint
public delete(name) {
fooService.deleteFoo2(name)
}
}
fooService {
public void deleteFoo2(String name) {
//delete logic here
deleteFoo(name.getId())
}
#CacheEvict(value = "cache1", key = "#id")
public void deleteFoo(Long id) {
//delete logic here
}
}
Why are my #CacheEvict annotations only called when the method is called straight from the controller?
I'm using Redis as the caching mechanism.
Aop is not worinking when your method is called inside the class.
It is working when method is called by another class.
So you can define deleteFoo in another service.
To make spring aspect intercept #Cache* annotations you have to make external call. If you don't like to call this method from another object, use bean self-invocation approach. In this case your class is presented as two objects and one calls the other:
#Resource private FooController thisBean;
public delete(id) {
thisBean.deleteFoo(id)
}
#CacheEvict(value = "cache1", key = "#id")
public void deleteFoo(Long id) {}
Versions used: spring-data-neo4j 4.2.0-BUILD-SNAPSHOT / neo4j-ogm 2.0.6-SNAPSHOT
I'm having problems to correctly fetch relationship entities.
The following fetch calls don't return consistent results (executed in the same transaction):
session.query("MATCH (:A)-[b:HAS_B]-(:C) RETURN count(b) as count") returns 1
session.query("MATCH (:A)-[b:HAS_B]-(:C) RETURN b") correctly returns the relationship entity as a RelationshipModel object
session.query(B.class, "MATCH (:A)-[b:HAS_B]-(:C) RETURN b") returns null !
Important remark: When all operations (create, fetch) are done in the same transaction, it seems to be fine.
I have been able to implement a workaround by using session.query(String, Map) to query the relationship entity and map it by myself into my POJO.
#NodeEntity
public class A {
public A () {}
public A (String name) {
this.name = name;
}
#GraphId
private Long graphId;
private String name;
#Relationship(type="HAS_B", direction=Relationship.OUTGOING)
private B b;
}
#RelationshipEntity(type="HAS_B")
public class B {
public B () {}
public B (String name, A a, C c) {
this.name = name;
this.a = a;
this.c = c;
}
#GraphId
private Long graphId;
#StartNode
private A a;
#EndNode
private C c;
private String name;
}
#NodeEntity
public class C {
public C () {}
public C (String name) {
this.name = name;
}
#GraphId
private Long graphId;
private String name;
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes={MyTest.TestConfiguration.class})
public class MyTest {
#Autowired
private MyBean myBean;
#Configuration
#EnableAutoConfiguration
#EnableTransactionManagement
#EnableNeo4jRepositories("com.nagra.ml.sp.cpm.core.repositories")
public static class TestConfiguration {
#Bean
public org.neo4j.ogm.config.Configuration configuration() {
org.neo4j.ogm.config.Configuration config = new org.neo4j.ogm.config.Configuration();
config.driverConfiguration().setDriverClassName("org.neo4j.ogm.drivers.embedded.driver.EmbeddedDriver");
return config;
}
#Bean
public SessionFactory sessionFactory() {
return new SessionFactory(configuration(), "com.nagra.ml.sp.cpm.model");
}
#Bean
public Neo4jTransactionManager transactionManager() {
return new Neo4jTransactionManager(sessionFactory());
}
#Bean
public MyBean myBean() {
return new MyBean();
}
}
#Test
public void alwaysFails() {
myBean.delete();
myBean.create("1");
try { Thread.sleep(2000); } catch (InterruptedException e) {} //useless
myBean.check("1"); // FAILS HERE !
}
#Test
public void ok() {
myBean.delete();
myBean.createAndCheck("2");
}
}
#Transactional(propagation = Propagation.REQUIRED)
public class MyBean {
#Autowired
private Session neo4jSession;
public void delete() {
neo4jSession.query("MATCH (n) DETACH DELETE n", new HashMap<>());
}
public void create(String suffix) {
C c = new C("c"+suffix);
neo4jSession.save(c);
A a = new A("a"+suffix);
neo4jSession.save(a);
B bRel = new B("b"+suffix, a, c);
neo4jSession.save(bRel);
}
public void check(String suffix) {
//neo4jSession.clear(); //Not working even with this
Number countBRels = (Number) neo4jSession.query("MATCH (:A)-[b:HAS_B]-(:C) WHERE b.name = 'b"+suffix+"' RETURN count(b) as count", new HashMap<>()).iterator().next().get("count");
assertEquals(1, countBRels.intValue()); // OK
Iterable<B> bRels = neo4jSession.query(B.class, "MATCH (:A)-[b:HAS_B]-(:C) WHERE b.name = 'b"+suffix+"' RETURN b", new HashMap<>());
boolean relationshipFound = bRels.iterator().hasNext();
assertTrue(relationshipFound); // FAILS HERE !
}
public void createAndCheck(String suffix) {
create(suffix);
check(suffix);
}
}
This query session.query(B.class, "MATCH (:A)-[b:HAS_B]-(:C) RETURN b") returns only the relationship but not the start node or end node and so the OGM cannot hydrate this. You need to always return the start and end node along with the relationship like session.query(B.class, "MATCH (a:A)-[b:HAS_B]-(c:C) RETURN a,b,c")
The reason it appears to work when you both create and fetch data in the same transaction is that the session already has a cached copy of a and c and hence b can be hydrated with cached start and end nodes.
Firstly, please upgrade from OGM 2.0.6-SNAPSHOT to 2.1.0-SNAPSHOT. I have noticed some off behaviour in the former which might be one part of the issue.
Now on to your test. There are several things going on here which are worth investigating.
Use of #DirtiesContext: You don't seem to be touching the context and if you are using it to reset the context between tests so you get a new Session/Transaction then that's going about it the wrong way. Just use #Transactional instead. The Spring JUnit runner will treat this in a special manner (see next point).
Being aware that Transactional tests automatically roll back: Jasper is right. Spring Integration Tests will always roll back by default. If you want to make sure your JUnit test commits then you will have to #Commit it. A good example of how to set up your test can be seen here.
Knowing how Spring Transaction proxies work. On top of all this confusion you have to make sure you don't simply call transactional method to transactional method in the same class and expect Spring's Transactional behaviour to apply. A quick write up on why can be seen here.
If you address those issues everything should be fine.