How to cache Spring Data JPA Projections - spring-boot

I am running Spring Boot 1.5.1 with Spring Data JPA repositories. I have added a method to my User repository that makes use of JPA projections(UserProfile) which works great. I now wish to cache the results of that method in my Service layer which should return a result of type Page< UserProfile > as shown
The JPA Projection.
public interface UserProfile extends Serializable {
long getId();
#Value("#{target.firstname} #{target.othernames}")
String getFullName();
String getFirstname();
String getOthernames();
String getGender();
String getEnabled();
#Value("#{T(System).currentTimeMillis()-target.birthday.getTime()}")
long getBirthday();
}
The User Entity.
#Entity
#Cacheable(true)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User implements Serializable {
private static final long serialVersionUID = 6756059251848061768L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column
private String firstname;
#Column
private String othernames;
#Column
private String gender;
#Column
private String photoname;
#Column
private Date birthday;
#Column
private String username;
#Column
private Boolean enabled;
#Column
private String password;
#ElementCollection
private Map<String,String> phonenumbers = new HashMap<String,String>(0);
#JsonBackReference
#OneToMany(cascade = CascadeType.ALL, orphanRemoval=true)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<Address> addresses = new ArrayList<Address>(0);
//Omitted Getters and Setters
#Override
public int hashCode() {...}
#Override
public boolean equals(Object obj) {...}
}
The User repository.
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
public Page<UserProfile> findAllUserProfilesBy(Pageable pageable);
}
The User service implementation.
#Service
#Transactional(readOnly=true)
public class UserServiceImpl implements UserService {
#Autowired
UserRepository UserRepository;
#Override
#Cacheable("users")
public Page<UserProfile> findAllUserProfiles(Pageable pageable) {
//simulateSlowService();
return UserRepository.findAllUserProfilesBy(pageable);
}
}
However I get the following exception when the service method gets called.
java.lang.RuntimeException: Class org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor does not implement Serializable or externalizable
How should I go about caching the result of the service method?
Any help is greatly appreciated.

Related

how to properly design a controller and a jsp page for an entity that has three keys, two external and one internal?

I'm trying to make a Spring MVC application.I have 4 entities(Company,Pass_in_trip,Passenger,Trip) Pass_in_trip has 3 keys consisting of Passenger, Trip and Timestamp, I don't know how to properly issue a key and how to transfer it through the jsp page to the controller, and how to issue the controller itself, can anyone tell me?and also an interesting question is how to make a request to the database to search for a record using three keys.
Thanks
here's what I was able to write at the moment, see if there are any errors somewhere
#Entity
#Table(name="company")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name="id_comp")
private int id_comp;
#Column(name="name")
private String name;
//Getters and Setters
#Entity
#Table (name="pass_in_trip")
public class Pass_in_trip implements Serializable {
#EmbeddedId
private KeysPass_in_trip key=new KeysPass_in_trip();
#Column(name="place")
private String place;
//Getters and Setters
#Embeddable
public class KeysPass_in_trip implements Serializable{
#NotNull
#JoinColumn(name="date")
private Timestamp date=new Timestamp(System.currentTimeMillis());
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_psg")
private Passenger id_psg=new Passenger();
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "trip_no" )
private Trip trip_no=new Trip();
//Getters and Setters
//#Override hashCode and equals
#Entity
#Table(name="passenger")
public class Passenger implements Serializable {
#Column(name="name")
private String name;
#NotNull
#Id
#Column(name="id_psg")
#GeneratedValue(strategy = IDENTITY)
private int id_psg;
//Getters and Setters
#Entity
#Table(name="trip")
public class Trip implements Serializable {
#NotNull
#Id
#Column(name="trip_no")
private int trip_no;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "id_comp")
private Company comp=new Company();
#Column(name="plane")
private String plane;
#Column(name="town_from")
private String town_from;
#Column(name="town_to")
private String town_to;
#Column(name="time_out")
private Timestamp time_out;
#Column(name="time_in")
private Timestamp time_in;
//Getters and Setters
Conroller
#Controller
#RequestMapping("/pass_in_trip/")
public class Aero_Controller_Pass_in_trip {
#Autowired
private Aero_DAO service;
public void setService(Aero_DAO service) {
this.service = service;
}
#RequestMapping(method=RequestMethod.GET)
public String list(Model uiModel) {
List <Pass_in_trip> pass_in_trip=service.findallPass_in_trip();
uiModel.addAttribute("pass_in_trip",pass_in_trip);
return "/pass_in_trip/list";
}
#PreAuthorize("hasRole('ROLE_Admin')")
#RequestMapping(value="delete/{id}",method=RequestMethod.GET)
public String delete(#PathVariable("id")int id, Model uiModel) {
if(service.findByIdPass_in_Trip(id)!=null)
service.delete_Pass_in_trip(id);
return "redirect:/pass_in_trip/";
}
#PreAuthorize("hasRole('ROLE_Admin')")
#RequestMapping(value="update/{id}",method=RequestMethod.GET)
public String updateform(#PathVariable("id")int id, Model uiModel) {
System.out.println("upform");
uiModel.addAttribute("pass_in_trip",service.findByIdPass_in_Trip(id));
System.out.println("upform2");
return "/pass_in_trip/edit";
}
#RequestMapping(value="update/0",method=RequestMethod.GET)
public String newform(Model uiModel) {
System.out.println("Привет!");
return "/pass_in_trip/edit";
}
#PreAuthorize("hasRole('ROLE_Admin')")
#RequestMapping(value="update/{id}",method = RequestMethod.POST)
public String update(Pass_in_trip pass_in_trip,BindingResult bindingResult,Model uiModel,HttpServletRequest httprervletrequest , RedirectAttributes redirectatributes) {
if (bindingResult.hasErrors()) {
uiModel.addAttribute("pass_in_trip", pass_in_trip);
return "pass_in_trip/update";}
service.save(pass_in_trip);
return "redirect:/pass_in_trip/";
}
}
List.jsp
interested in this part:
<s:authorize access="hasRole('ROLE_Admin')">
<td> To change </td>
<td> Delete </td>
</s:authorize>

Consider defining a bean of type 'int' in your configuration[SpringBoot]

its my first time crating api in spring boot, i'm trying to create transaction api. when i'm running the application i'm getting this error
Description:
Parameter 0 of constructor in TransactionService.transactionService.modal.TransactionRequest required a bean of type 'int' that could not be found.
Action:
Consider defining a bean of type 'int' in your configuration.
Modal package:
TransactionEntity
#Getter
#Setter
#Builder
#Entity
public class TransactionEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int transactionId;
#NotNull
#Column(unique = true)
private UUID externalId;
#NotNull
private int userId;
#NotNull
private int merchantId;
#NotNull
private int clientReferenceId;
#NotNull
private double amount;
#Enumerated(EnumType.STRING)
#NotNull
private TransactionStatus status;
#NotNull
private String createdBy;
private String updatedBy;
#NotNull
private LocalDateTime createdAt;
#NotNull
private LocalDateTime updatedAt;
}
TransactionRequest
#Component
#Data
#Builder
public class TransactionRequest {
private int userId;
private int merchantId;
private int clientReferenceId;
private double amount;
private String createdBy;
}
TransactionResponse
#Component
#Data
#Builder
public class TransactionResponse {
private int userId;
private int merchantId;
private int clientReferenceId;
private double amount;
private LocalDateTime createdAt;
private TransactionStatus status;
}
TransactionDao
#Component
// Dao class
public class TransactionDao {
#Autowired
TransactionRepository transactionRepository;
TransactionEntity transactionEntity;
public TransactionResponse createTransaction(TransactionRequest transactionRequest){
LocalDateTime cuurentTime = LocalDateTime.now();
transactionEntity.builder().userId(transactionRequest.getUserId())
.merchantId(transactionRequest.getMerchantId())
.clientReferenceId(transactionRequest.getClientReferenceId())
.amount(transactionRequest.getAmount())
.createdBy(transactionRequest.getCreatedBy())
.createdAt(cuurentTime)
.updatedAt(cuurentTime)
.externalId(UUID.randomUUID())
.status(TransactionStatus.CREATED);
transactionRepository.save(transactionEntity);
return TransactionResponse.builder().status(transactionEntity.getStatus())
.createdAt(transactionEntity.getCreatedAt()).build();
}
}
TransactionService
#Service
public class TransactoinService {
#Autowired
public TransactionDao transactionDao;
public TransactionResponse createTransaction(TransactionRequest transactionRequest){
return transactionDao.createTransaction(transactionRequest);
}
}
TransactionController
#RestController
public class TransactionController {
#Autowired
TransactoinService transactoinService;
#PostMapping
TransactionResponse createTransaction(#RequestBody TransactionRequest transactionRequest){
return transactoinService.createTransaction(transactionRequest);
}
}
The TransactionRequest is annotated as #Component so spring boot autoscan will try to create a #Bean out that class.
It is also annotated with #Data so at the time of creating the bean Spring boot is trying to inject other beans as arguments into the all args constructor, and it is not finding an "int" bean to inject into the constructor.
I am guessing that the transaction response should not be a #Component or at least not a Singleton bean.
You should not create your POJO classes as a Spring Bean. Remove #Component annotation in your TransactionRequest and TransactionResponse POJO classes.

Are there #MappedSuperclass in Spring Reactive Data (R2DBC)

I have a super Entity class like this:
#Getter
#Setter
#NoArgsConstructor
public class GenericEntity {
#Id
private Long id;
#JsonIgnore
#CreatedBy
private Long createdBy;
#JsonIgnore
#CreatedDate
private Long createdDate;
#JsonIgnore
#LastModifiedBy
private Long updatedBy;
#JsonIgnore
#LastModifiedDate
private Long updatedDate;
#JsonIgnore
#Version
private Integer version = 0;
}
and a Role class extends from GenericEntity like this:
#Getter
#Setter
#NoArgsConstructor
public class Role extends GenericEntity {
private String name;
private String desc;
private Integer sort;
}
And after that I have interface RoleRepo like this:
#Repository
public interface RoleRepo extends ReactiveCrudRepository<Role, Long>;
In Router function, I have 2 handler methods
private Mono<ServerResponse> findAllHandler(ServerRequest request) {
return ok()
.contentType(MediaType.APPLICATION_JSON)
.body(roleRepo.findAll(), Role.class);
}
private Mono<ServerResponse> saveOrUpdateHandler(ServerRequest request) {
return ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(request.bodyToMono(Role.class).flatMap(role -> {
return roleRepo.save(role);
}), Role.class);
}
The method findAllHandler works fine, but the saveOrUpdateHandler throw exception like this:
java.lang.IllegalStateException: Required identifier property not found for class org.sky.entity.system.Role!
at org.springframework.data.mapping.PersistentEntity.getRequiredIdProperty(PersistentEntity.java:105) ~[spring-data-commons-2.2.0.M2.jar:2.2.0.M2]
at org.springframework.data.r2dbc.function.convert.MappingR2dbcConverter.lambda$populateIdIfNecessary$0(MappingR2dbcConverter.java:85) ~[spring-data-r2dbc-1.0.0.M1.jar:1.0.0.M1]
But when I move
#Id
private Long id;
from GenericEntity class to Role class, the two methods work fine.
Are there any Annations #MappedSuperclass/JPA in Spring Reactive Data like that
I wish the id field in GenericEntity for all extends class
Thanks for your help
Sorry, my English so bad
I had a similar problem and after some search, I didn't find an answer to your question, so I test it by writing code and the answer is spring data R2DBC doesn't need #Mappedsuperclass. it aggregates Role class properties with Generic class properties and then inserts all into the role table without the need to use any annotation.

Spring framework JPA2 #OneToMany relationship on GAE

I'm working on GAE with Spring framework 4.0.5 and Spring Data jpa 1.3.5.
I' trying to retrieve a list of object in a OneToMany relation, but I receive the following error:
You have just attempted to access field "organizationMemberships" yet this field was not detached when you detached the object. Either dont access this field, or detach it when detaching the object.
The User Entity is:
#Entity
#Table(name="users")
#NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO )
private Long id;
private String email;
private String password;
private String salt;
private String slug;
private int status;
private String username;
//bi-directional one-to-one association to UserContact
#OneToOne(mappedBy="user")
private UserContact userContact;
//bi-directional one-to-one association to UserDetail
#OneToOne(mappedBy="user", cascade = CascadeType.ALL)
private UserDetail userDetail;
//bi-directional many-to-one association to UsersApisession
#OneToMany(mappedBy="user")
private List<UsersApisession> usersApisessions;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrganizationMember> organizationMemberships;
...
}
While the OrganizationMember is:
#Entity
#Table(name="organization_members")
#NamedQuery(name="OrganizationMember.findAll", query="SELECT o FROM OrganizationMember o")
public class OrganizationMember implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO )
private Long id;
private String email;
private int status;
//bi-directional many-to-one association to Organization
#ManyToOne
private Organization organization;
//bi-directional many-to-one association to User #JoinColumn(name="member_id")
#ManyToOne(fetch = FetchType.LAZY)
private User user;
//bi-directional many-to-one association to OrganizationPosition
#ManyToOne
#JoinColumn(name="position_id")
private OrganizationPosition organizationPosition;
...
}
My userRepository is an interface according to Spring JPA:
#Transactional
public interface UserRepository extends JpaRepository<User, Long>{
User findByEmail(String email);
User findBySlug(String slug);
}
The code of my controller is the following:
#Controller
public class ProtectedSiteController {
...
#Autowired
private UserRepository userRepo;
#RequestMapping(method = RequestMethod.GET, value="/afterLogin")
public String afterLogin(HttpServletRequest request, HttpServletResponse response){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userRepo.findByEmail(auth.getName());
List<OrganizationMember> memberList = user.getOrganizationMemberships();
...
}
}
This is my configuration for transaction:
#Configuration
#EnableJpaRepositories("com.example.repository")
#EnableTransactionManagement
public class JpaApplicationConfig {
private static final Logger logger = Logger
.getLogger(JpaApplicationConfig.class.getName());
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
Map<String, String> map = new HashMap<String, String>();
map.put("datanucleus.NontransactionalRead","true");
map.put("datanucleus.NontransactionalWrite","false");
map.put("datanucleus.storeManagerType","rdbms");
map.put("datanucleus.autoCreateSchema" ,"false");
map.put("datanucleus.validateTables" ,"false");
map.put("datanucleus.validateConstraints" ,"false");
map.put("datanucleus.jpa.addClassTransformer" ,"true");
map.put("datanucleus.singletonEMFForName", "true");
LocalContainerEntityManagerFactoryBean lce= new LocalContainerEntityManagerFactoryBean();
lce.setPersistenceProviderClass(org.datanucleus.api.jpa.PersistenceProviderImpl.class);
DriverManagerDataSource dmds = new DriverManagerDataSource();
dmds.setDriverClassName("com.mysql.jdbc.Driver");
dmds.setUrl("jdbc:mysql://localhost:3306/example");
dmds.setUsername("example");
dmds.setPassword("example");
lce.setDataSource(dmds);
lce.setPackagesToScan("com.example.models");
lce.setJpaPropertyMap(map);
lce.setLoadTimeWeaver(new org.springframework.instrument.classloading.SimpleLoadTimeWeaver());
return lce;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
logger.info("Loading Transaction Manager...");
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(emf);
return txManager;
}
#Bean
public PersistenceAnnotationBeanPostProcessor postProcessor(){
return new PersistenceAnnotationBeanPostProcessor();
}
}
I've followed either the GAE tutorials either the Spring ones. What is my mistake?
Thank you.
The fast trick is
put on method afterLogin(..) #Transactional..
the problem is that when user returns, the transaction is closed and for this reason cannot retrieve Organization Membership (detached).
Another solution is change the fetch type in EAGER, #OneToMany by default is LAZY.
#Service
public class ServiceClass {
...
#Autowired
private UserRepository userRepo;
#Transactional
public List<OrganizationMember> method(String name){
User user = userRepo.findByEmail(name);
return user.getOrganizationMemberships();
}
}
#Controller
public class ProtectedSiteController {
...
#Autowired
private ServiceClass serviceClass;
#RequestMapping(method = RequestMethod.GET, value="/afterLogin")
public String afterLogin(HttpServletRequest request, HttpServletResponse response){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<OrganizationMember> memberList = serviceClass.method(auth.getName());
...
}
}
I hope I've given you all the answers about your question.

Why the record is posted twice in the database?

Can you tell me, why the record is posted twice in the database. I think. this happens because I use save() method. But shouldn't I save the master-entity and dependent-entity separately?
Controller method:
#RequestMapping(value = "/addComment/{topicId}", method = RequestMethod.POST)
public String saveComment(#PathVariable int topicId, #ModelAttribute("newComment")Comment comment, BindingResult result, Model model){
Topic commentedTopic = topicService.findTopicByID(topicId);
commentedTopic.addComment(comment);
// TODO: Add a validator here
if (!comment.isValid() ){
return "//";
}
// Go to the "Show topic" page
commentService.saveComment(comment);
return "redirect:../details/" + topicService.saveTopic(commentedTopic);
}
Services:
#Service
#Transactional
public class CommentService {
#Autowired
private CommentRepository commentRepository;
public int saveComment(Comment comment){
return commentRepository.save(comment).getId();
}
}
#Service
#Transactional
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public int saveTopic(Topic topic){
return topicRepository.save(topic).getId();
}
}
Model:
#Entity
#Table(name = "T_TOPIC")
public class Topic {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
#Enumerated(EnumType.STRING)
private Tag topicTag;
private String name;
private String text;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();
}
#Entity
#Table(name = "T_COMMENT")
public class Comment
{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="TOPIC_ID")
private Topic topic;
#ManyToOne
#JoinColumn(name="USER_ID")
private User author;
private String text;
private Date creationDate;
}
In this concrete case, you do not need to save the master and the client.
Saving the master or the client would be enough (with this concrete mapping)
But I think the main problem is that you do not have a good equals method in your Comment so your ORM Provider think that there are two different comments, and therefore store them twice.

Resources