Spring boot #CachePut, how it works - spring

I'm trying to update values in spring cache, however #CachePut does not replace actual value, but put another value with the same key:
Cachename: noTimeCache:
Key: SYSTEM_STATUS, val: ON
Key: SYSTEM_STATUS, val: OFF
1.Why?
My cache service:
#CachePut(value = "noTimeCache", key = "#setting.getSetting().name()")
public String updateGlobalCacheValue(GlobalSettings setting) {
System.out.println("Setting: " + setting.getSetting().name() + ", val: " + setting.getValue());
return setting.getValue();
}
I know it's looks funny but i need to work with jpa repositories. Maybe someone have better solution.
2.Why can't I use function argument as #Cacheable key like this?
#Cacheable(value = "noTimeCache", key = "#setting.name()", unless = "#result == null")
#Query("SELECT gs.value FROM GlobalSettings gs WHERE gs.setting = ?1")
String getGlobalSettingValue(Settings setting);
It tells me setting is null.
3.Is the way to override #Repositository interface methods, for example save() to add annotation #Cacheable to them?

1-#CachePut does not replace actual value, but put another value :delete key = "#setting.name" in that way keygenarator will use the hashcode of GlobalSettings(verify if the hashcode is implemented correctly)and also verify if the name of the cache "noTimeCache" is not specified to other methods.
2-see this link http://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html if the "setting" parameter is an attribute in GlobalSettings you can
change the SpEL expression #setting.getSetting().name() to #setting.setting.name
3-You can do the following if you are using java 6 (no idea if this is possible with java 7 or 8 ) :
public interface GlobalSettingsRepository extends JpaRepository<GlobalSettings, Long>{
#Override
#CacheEvict(value = {"noTimeCache"}, allEntries = true)
GlobalSettings save(GlobalSettings globalSettings);
}

Thanks for answers. It helps me solve problems.
#CachePut replace values, but there was a problem in keys. I use something like this:
#Cacheable(value = "globalSettings", unless = "#result == null")
#Query("SELECT gs.value FROM GlobalSettings gs WHERE gs.setting = ?1")
String getGlobalSettingValue(Settings setting);
Settings is enum and key for default is enum name, for example SYSTEM_STATUS.
and this:
#Cacheable(value = "globalSettings", key = "#setting.name()")
public String getGlobalSettingEnumValue(Settings setting) {
return Settings.valueOf(setting.name()).getDefaultValue();
}
will also save key as STSYEM_STATUS.
Key's was the same but cache interprets this like 2 different cache values, I don't know why.
U can't use variable name in repository class like #setting, it must be argument index like #p0, probably beacause of spring data use his own proxy classes.
This solve all my problems and now cache work properly.

Related

MapStruct Spring Page to custom object conversion includes check

I am using MapStruct to convert a Page object to a custom object of my application. I am using this mapping in order to convert the content field of the Page object to a list of custom objects found in my data model:
#Mapping(target = "journeys", source = "content")
While this works OK and does convert the elements when content is present, this does not work correctly in case of no Page content. Taking a look at the code seems to show that the following check is added in the generated mapper class:
if ( page.hasContent() ) {
List<JourneyDateViewResponseDto> list = page.getContent();
journeyDateViewPageResponseDto.setJourneys( new ArrayList<JourneyDateViewResponseDto>( list ) );
}
When this is added the mapping action of the inner objects is omitted, meaning that I end up with a null list. I am not really sure as to why and how this check is added but I would like to find a way of disabling it and simply end up with an empty list of elements. Is there a way this can be done using MapStruct?
MapStruct has the concept of presence checkers (methods that have the pattern hasXXX). This is used to decide if a source property needs to be mapped.
In case you want to have a default value in your object I would suggest making sure that your object is instantiated with an empty collection or provide an #ObjectFactory for your object in which you are going to set the empty collection.
e.g.
Default value in class
public class JourneyDateViewPageResponseDto {
protected List<JourneyDateViewResponseDto> journeys = new ArrayList<>();
//...
}
Using #ObjectFactory
#Mapper
public interface MyMapper {
JourneyDateViewPageResponseDto map(Page< JourneyDateViewResponseDto> page);
#ObjectFactory
default JourneyDateViewPageResponseDto createDto() {
JourneyDateViewPageResponseDto dto = new JourneyDateViewPageResponseDto();
dto.setJourneys(new ArrayList<>());
return dto;
}
}
#Mapping(target = "journeys", source = "content", defaultExpression = "java(java.util.List.of())")

Retrieve generated ID using MyBatis Annotation Spring Boot

I am trying to learn MyBatis. How to do I get the auto-generated ID after I have inserted a statement using the #InsertAnnotation.
Example of my code:
#Insert("INSERT INTO user(name, mobile, password) VALUES(#{name}, #{mobile}, #{password})")
#SelectKey(statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", before = false, resultType = Long.class)
Long insertUser(User user);
I want to get the generated id as the return from the insert method.
#SelectKey is for legacy drivers.
For recent drivers, you should use useGeneratedKeys.
We have an FAQ entry explaining how to do it with XML mapper.
With annotation, it would look as follows.
#Insert("INSERT INTO user(name, mobile, password) VALUES(#{name}, #{mobile}, #{password})")
#Options(useGeneratedKeys = true, keyProperty = "id")
int insertUser(User user);
Note that #Insert method returns the number of updated rows, not the generated key.
The generated key is assigned to the property of the parameter specified by keyProperty i.e. User.id in your case.
For some databases, you might need to specify keyColumn as well.
If it didn't work, please add versions of DB, driver and MyBatis to the question.
#Select("insert into security.users (name,email,password) values(#{user.name}, #{user.email}, #{user.password}) returning id")
#Result(column = "id")
this worked me. but i try a many time with #INSERT annotation not worked;

CaffeineCache is not refreshed immediately after put operation in Spring Boot application

I have a random issue with CaffeineCache in my Spring Boot application. The problem is with an integration test that makes next
find user
delete it
find it again
It seems that sometimes cache doesn't not refreshes on time before the second call of find that comes immediately after delete.
Here is a simplified signature of find method
#Cacheable(cacheNames = "name", key = "{#config.id, #userId}", unless = "#result == null")
public User find(SomeConfig config, String userId) {
// ...
}
And a simplified signature of delete
#Caching(put = {
#CachePut(cacheNames = "someOtherCache", key = "#userId.technicalId"),
#CachePut(cacheNames = "name", key = "{#config.id, #userId}")
})
public User delete(SomeConfig config, String userId) {
// ...
}
I suppose that after call delete cache is not updated immediately and that's why method find is not called the second time. It happens 1 times from 10.
Any ideas about fix?

How to fallback on a enum if values don't match in Moshi

I have an enum class and would like it to fallback to a specific enum value if values don't match any of them. I found a Moshi issue that talks about using EnumJsonAdapter but I don't see any public class for me to use.
I'm using Moshi 1.8.0
Any ideas on how to achieve this or is writing a custom JSON adapter the only way to go?
There is an adapters artifact for extra adapters like EnumJsonAdapter.
https://github.com/square/moshi/tree/master/moshi-adapters/src/main/java/com/squareup/moshi/adapters
I created this generic object to create EnumJsonAdaprters:
object NullableEnumMoshiConverter {
fun <T : Enum<T>> create(enumType: Class<T>, defaultValue: T? = null): JsonAdapter<T> =
EnumJsonAdapter.create(enumType)
.withUnknownFallback(defaultValue)
.nullSafe()
}
It handles null values in the JSON as well.
You should Add it in the builder method like this:
Moshi.Builder().apply {
with(YourEnumClassName::class.java) {
add(this, NullableEnumMoshiConverter.create(this))
}
}.build()

spring-data-mongodb Query.fields().slice() on #DBRef field throws MappingException

I have a problem with sliced access to some #DBRef field in my model. I use spring-data-mongodb-1.8.0.M1.jar
The model is like:
class Model {
....
#DBRef
List<OtherModel> members;
...
}
and I need sliced access to the members variable.
I use this query:
ObjectId objectId = new ObjectId("55c36f44f359d8a455a21f68");
Query query = new Query(Criteria.where("_id").is(objectId));
query.fields().slice("members", pageable.getOffset(), pageable.getPageSize());
List<Model> models = mongoTemplate.findOne(query, Model.class);
But I get this exception:
org.springframework.data.mapping.model.MappingException: No id property found on class class [Ljava.lang.Integer;
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.createDBRef(MappingMongoConverter.java:842)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.toDBRef(MappingMongoConverter.java:329)
at org.springframework.data.mongodb.core.convert.QueryMapper.createDbRefFor(QueryMapper.java:460)
at org.springframework.data.mongodb.core.convert.QueryMapper.convertAssociation(QueryMapper.java:417)
at org.springframework.data.mongodb.core.convert.QueryMapper.convertAssociation(QueryMapper.java:378)
at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedKeyword(QueryMapper.java:257)
at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObjectForField(QueryMapper.java:200)
at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:123)
at org.springframework.data.mongodb.core.MongoTemplate.doFindOne(MongoTemplate.java:1647)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:563)
at org.springframework.data.mongodb.core.MongoTemplate.findOne(MongoTemplate.java:558)
where a field
boolean needsAssociationConversion = property.isAssociation() && !keyword.isExists();
is set. It checks against isExists, but not against something like isSliced (which does not yet exist) and therefore is evaluated to true and, as a cause, tries to convert the non-existing association which is, in this case, just the slice-directive (an integer array). When I set the variable needsAssociationConversion to false while debugging, as if a kind of keyword.isSlice() check was done, everything works fine.
Is this a bug?
Executable project is here
https://github.com/zhsyourai/sliceDemo

Resources