I have a simple spring boot with redis application that i am building to cache master data of 3 entities emails, products and countries.
i have used the spring boot starter dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.3.RELEASE</version>
</dependency>
also i am using started configuration in my application.properties file
spring.cache.type=redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.cache.redis.key-prefix=masterDataApp
spring.cache.redis.use-key-prefix=true
The cacheable methods are
#Service
public class MasterDataService {
#Cacheable(value="EMAILS")
public List<Email> getEmails() {
System.out.println(" Loading Data from getEmails");
return emails;
}
#Cacheable(value="PRODUCTS")
public List<Product> getProducts() {
System.out.println(" Loading Data from getProducts");
return products;
}
#Cacheable(value="COUNTRIES")
public List<Country> getCountries() {
System.out.println(" Loading Data from getCountries");
return countries;
}
}
The issue is once i load one of the methods from this class, the other 2 methods return the same data as the first method even though they return different Lists and have different Values Cacheable annotation. What am i doing wrong?
The solution is to have a keygeneratory. and add it to the annotation of the method.
This helps in segregating the caches
#Cacheable(value="PRODUCTS", keyGenerator = "customKeyGenerator")
public List<Product> getProducts() {
System.out.println(" Loading Data from getProducts");
return products;
}
#Cacheable(value="COUNTRIES", keyGenerator = "customKeyGenerator")
public List<Country> getCountries() {
System.out.println(" Loading Data from getCountries");
return countries;
}
#Bean("customKeyGenerator")
public KeyGenerator keyGenerator() {
return new CustomKeyGenerator();
}
public class CustomKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params) {
String key = target.getClass().getSimpleName() + "_"
+ method.getName() + "_"
+ StringUtils.arrayToDelimitedString(params, "_");
System.out.println(key);
return key;
}
}
But the question still remains what is the value of value in the annotation as it does nothing to segregate the caches
Related
The purpose of this question is to find out if the codes are written with the right approach. Let's do CRUD operations on categories and posts in the blog website project. To keep the question short, I shared just create and update side.
(Technologies used in the project: spring-boot, mongodb)
Let's start to model Category:
#Document("category")
public class Category{
#Id
private String id;
#Indexed(unique = true, background = true)
private String name;
#Indexed(unique = true, background = true)
private String slug;
// getter and setter
Abstract BaseController class and IController Interface is created for fundamental level save, delete and update operations. I shared below controller side:
public interface IController<T>{
#PostMapping("/save")
ResponseEntity<BlogResponse> save(T object);
#GetMapping(value = "/find-all")
ResponseEntity<BlogResponse> findAll();
#GetMapping(value = "/delete-all")
ResponseEntity<BlogResponse> deleteAll();
}
public abstract class BaseController<T extends MongoRepository<S,String>, S> implements IController<S> {
#Autowired
private T repository;
#Autowired
private BlogResponse blogResponse;
#PostMapping(value = "/save", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public #ResponseBody ResponseEntity<BlogResponse> save(S object) {
try {
S model = (S) repository.save(object);
String modelName = object.getClass().getSimpleName().toLowerCase();
blogResponse.setMessage(modelName + " is saved successfully").putData(modelName, object);
} catch (DuplicateKeyException dke) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage("This data is already existing!!!"), HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<BlogResponse>(blogResponse, HttpStatus.OK);
}
// delete, findAll and other controllers
#RestController
#RequestMapping(value = "category")
#RequestScope
public class CategoryController extends BaseController<ICategoryRepository, Category>{
// More specific opretions like findSlug() can be write here.
}
And finally BlogResponce component is shared below;
#Component
#Scope("prototype")
public class BlogResponse{
private String message;
private Map<String, Object> data;
public String getMessage() {
return message;
}
public BlogResponse setMessage(String message) {
this.message = message;
return this;
}
public BlogResponse putData(String key, Object object){
if(data == null)
data = new HashMap<String,Object>();
data.put(key,object);
return this;
}
public Map<String,Object> getData(){
return data;
}
#Override
public String toString() {
return "BlogResponse{" +
"message='" + message + '\'' +
", data=" + data +
'}';
}
}
Question: I am new spring boot and I want to move forward by doing it right. BlogResponse is set bean by using #Component annotation. This doc said that other annotations like #Controller, #Service are specializations of #Component for more specific use cases. So I think, I cant use them. BlogResponse is set prototype scope for create new object at each injection. Also it's life end after response because of #RequestScope. Are this annotations using correcty? Maybe there is more effective way or approach. You can remark about other roughness if it existing.
I am getting a DataTypeException when retrieving data since the upgrade to Spring Boot 2.4.x. It worked fine with 2.3.9.RELEASE.
org.jooq.exception.DataTypeException: No Converter found for types MyBaseType and MyInheritType1 at
org.jooq.impl.Tools.converterOrFail(Tools.java:1132) at
org.jooq.impl.Tools.converterOrFail(Tools.java:1148) at
org.jooq.impl.AbstractRecord.get(AbstractRecord.java:270) at
org.jooq.impl.AbstractResultQuery.fetchOne(AbstractResultQuery.java:576) at
org.jooq.impl.SelectImpl.fetchOne(SelectImpl.java:3019)
MyInheritType1 extends MyBaseType.
The classes are using lombok #Data, so they should have the proper setters.
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "_class")
#JsonSubTypes(
{
#JsonSubTypes.Type(value = MyInheritType1.class, name = "Type1"),
#JsonSubTypes.Type(value = MyInheritType2.class, name = "Type2")
})
public class MyBaseType
{
private UUID id;
private String disclaimerLongText = "";
private LocalDateTime creationTime;
private Map<UUID, String> images = new HashMap<>();
}
The inherited type:
#Data
public class MyInheritType1 extends MyBaseType
{
private String baseMap;
private EnumType someEnum;
private List<LayerType> layerTypes = new ArrayList<>();
private double[] center;
}
I retrieve the data like this:
return dsl.select(PROJECT.DETAILS).from(PROJECT)
.where(PROJECT.ID.eq(id.toString()))
.fetchOne(PROJECT.DETAILS, MyInheritType1.class);
PROJECT.DETAILS is defined as this:
public final TableField<ProjectRecord, ProjectDetails> DETAILS = createField(DSL.name("details"), SQLDataType.JSONB.nullable(false), this, "", new ProjectDetailsBinding());
And ProjectDetailsBinding looks like this:
public class ProjectDetailsBinding extends JsonBBinding<MyBaseType>
{
#Override
protected Class<ProjectDetails> getBindingType()
{
return MyBaseType.class;
}
}
public abstract class JsonBBinding<T> implements Binding<JSONB, T>
{
private ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule());
protected abstract Class<T> getBindingType();
#Override
public Converter<JSONB, T> converter()
{
return new Converter<JSONB, T>()
{
#Override
public T from(JSONB o)
{
if (o == null)
return null;
try
{
return objectMapper.readValue(o.data(), getBindingType());
} catch (Exception e)
{
e.printStackTrace();
}
return null;
}
#Override
public JSONB to(T t)
{
try
{
return JSONB.valueOf(objectMapper.writeValueAsString(t));
} catch (JsonProcessingException e)
{
e.printStackTrace();
}
return null;
}
#Override
public Class<JSONB> fromType()
{
return JSONB.class;
}
#Override
public Class<T> toType()
{
return getBindingType();
}
};
}
[..]
}
Since it worked with 2.3.9.RELEASE, I am wondering what changed in Spring Boot or Jooq, that would cause this different behavior now?
Looks like https://github.com/jOOQ/jOOQ/issues/11762, fixed for 3.15.0 and 3.14.9, to be released soon. You can try building 3.14.9 from github or use a snapshot build from here: https://www.jooq.org/download/versions if you're licensed, to see if that fixes your issue.
Alternatively, you can try to use the fixed version of the DefaultConverterProvider and use that in your Configuration.
Since it worked with 2.3.9.RELEASE, I am wondering what changed in Spring Boot or Jooq, that would cause this different behavior now?
Typically, Spring Boot upgrades come with jOOQ upgrades. You could also downgrade your jOOQ dependency to what you were using before with Spring Boot 2.3.9
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'm using Spring Boot for a project, I'm stuck with lazy loading.
What I want to do is load data in my controller, then send to presentable object, that will extract needed information and the JSON serializer do the bad work to create my custom HTTP response.
the problem occurs when the UserPresentation class calls the folder getter, the error is the well known: could not initialize proxy - no Session.
Of course the default fetch is LAZY for the folder and I want this, but I don't know how to prepare the object to be usable in the Presentation.
I copy-pasted only Folder set to be clear and short, but I've more collection inside User class, all of them give me the same problem.
I know that I could call getter in controller just to initialize Collections, but I find this like an hardcoding, in fact if I want add something to presentable I need to do in controller too.
I've tried too with #Transactional but not works.
Here are my class:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID")
private Integer id;
#Column(unique = true)
private String email;
private String password;
#Enumerated(EnumType.STRING)
private Authority userAuthority;
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
private Set<Folder> ownFolders = new HashSet<>();
... getter setter
}
#RestController
public class UserController {
#GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
public CustomResponseEntity userInfo() {
User currentUser = loginService.getCurrentUser();
UserPresentation userPresentation = new UserPresentation(currentUser);
return ResponseManager.respondData(userPresentation);
}
}
public class UserPresentation implements Presentable {
private User user;
public UserPresentation(User user) {
this.user = user;
}
public Integer getId() {
return user.getId();
}
public String getEmail() {
return user.getUsername();
}
public String getAuthority() {
return user.getUserAuthority().name();
}
public boolean isEnabled() {
return user.isEnabled();
}
public Integer getOwnFolders() {
Set<Folder> folderList = user.getOwnFolders();
if (folderList == null)
return 0;
return folderList.size();
}
}
Last two just to be clear
public class ResponseManager {
// DATA
public static ResponseEntity respondData(Presentable presentable, String token) {
CustomResponse response = new DataResponse<>(presentable);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
public class DataResponse<T extends Presentable> extends CustomResponse {
private T data;
public T getData() {
return data;
}
private void setData(T data) {
this.data = data;
}
public DataResponse(T data) {
this.setData(data);
}
#Override
public String getType() {
return DATA;
}
}
I suppose you load the current user form the database with:
User currentUser = loginService.getCurrentUser();
and the getCurrentUser() method is transactional. You can either:
Use JPQL like this:
"select u from User u join fetch u.ownFolders where ... " to load the user's info (this way ownFolders relation is eagerly fetched)
or
Simply call user.getOwnFolders() inside getCurrentUser() to trigger
the fetch.
I found a way, even is a little bit dirty it allows me to do what I want without big change at the code.
Practically the problem occurs during the JSON serialization, that run outside of my control (somewhere inside Spring classes just before send HTTP response), so I manually serialized every Presentable object inside a #Transactional block just after its creation.
These are the changed classes:
public class UserPresentation implements Presentable {
private User user;
public UserPresentation(User user) {
this.user = user;
this.initialize() //ADDED (called here and in every other class that implements Presentable)
}
...getter and setter (which I want as JSON fields)
}
#RestController
public class UserController {
#Transactional //ADDED
#GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
public CustomResponseEntity userInfo() {
User currentUser = loginService.getCurrentUser();
UserPresentation userPresentation = new UserPresentation(currentUser);
return ResponseManager.respondData(userPresentation);
}
}
Before this fix, the interface was used only to use Polymorfism inside ResponseManager, so was empty
public interface Presentable {
default void initialize() {
try {
new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeJsonMappingException(e.getMessage());
}
}
}
I would suggest you use https://github.com/FasterXML/jackson-datatype-hibernate
The module supports datatypes of Hibernate versions 3.x , 4.x and 5.x; as well as some of the associated behavior such as lazy-loading and detection of transiency (#Transient annotation).
It knows how to handle Lazy loading after the session is closed , it will skip the json conversion for objects marked as Lazy fetch when outside session
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.8</version>
</dependency>
ObjectMapper mapper = new ObjectMapper();
// for Hibernate 4.x:
mapper.registerModule(new Hibernate4Module());
// or, for Hibernate 5.x
mapper.registerModule(new Hibernate5Module());
// or, for Hibernate 3.6
mapper.registerModule(new Hibernate3Module());
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/*
* Here we register the Hibernate4Module into an ObjectMapper, then set this * custom-configured ObjectMapper to the MessageConverter and return it to be * added to the HttpMessageConverters of our application
*/
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper hibernateAwareObjectMapper = new ObjectMapper();
hibernateAwareObjectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
hibernateAwareObjectMapper.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// Registering Hibernate5Module to support lazy objects
hibernateAwareObjectMapper.registerModule(new Hibernate5Module());
messageConverter.setObjectMapper(hibernateAwareObjectMapper);
return messageConverter;
}
}
XML config
<mvc:annotation-driven>
<mvc:message-converters>
<!-- Use the HibernateAware mapper instead of the default -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="path.to.your.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
I generate a list of instances which implement interface CheckInterface
using reflection, but some of those instances need to use JPA repository
,as they are new by reflection so Spring will not inject repository for them.
So how can I inject repository into these instance
Or how can I delegate these class to spring as #Bean and iterate them?
#Component
#Log4j
public class ValidationRegister {
public static HashMap<String, CheckItem> itemHashMap = new HashMap<>();
static {
if (itemHashMap.size() == 0) {
Reflections reflections = new Reflections(CheckItem.class.getPackage().getName());
Set<Class<? extends CheckItem>> itemClasses =
reflections.getSubTypesOf(CheckItem.class);
for (Class<? extends CheckItem> checkItemClass : itemClasses) {
try {
itemHashMap.put(checkItemClass.getName(), checkItemClass.newInstance());
} catch (Exception e) {
log.error("ValidationRegister fail : ", e);
}
}
}
}
}
public class LPHYCZ extends CheckItem {
#Autowired
ClaimDataEntityRepository claimDataEntityRepository;
#Override
public boolean check(ClaimRequest request, List<String> errorList) {
ClaimDataEntity claimDataEntity = claimDataEntityRepository.findByClaimId(request.getClaimId());
if (claimDataEntity != null) {
return true;
}
return false;
}
}
When I get instance of LPHYCZ from ValidationRegister.itemHashMap its
claimDataEntityRepository will be null
Just make your Reflections reflections a Spring Bean and get it injected into 'ValidationRegister' then use it to initialize your HashMap your hashmap still can be static, if you want it to be shared by multiple ValidationRegister instances, but the injection won't happen on static fields.