I have some model classes with relations to each other (User, Group, Message, etc). For many reasons (I'm not giving the details, but this is not a flexible decision) the relations are Lazy, and I want them to remain Lazy.
Sometimes I want to load some class collections. P.e. user.getGroups() or user.getMessages() but, because of the Lazy load, I need to call Hibernate.initialize() in the method of the DAO class, which is OK for me.
The question is, Is there a strategy to avoid declaring many DAO methods to load several combination of collections, reusing the methods?
Here is an example of what I want to avoid:
UserController:
public class UserController {
#Autowired
private UserService userService;
#RequestMapping(value = "/user/view/{id}", method = RequestMethod.GET)
public String view(#PathVariable Long id, Model model) {
// Choose one between the following, depending on the case
User user = userService.getUser(id);
User user = userService.getUserWithGroups(id);
User user = userService.getUserWithGroupsAndMessages(id);
//...
}
}
And the UserDAOImpl:
#Repository
public class UserDAOImpl implements UserDAO {
#Override
public User getUser(long id) {
return (User) this.getCurrentSession().get(User.class, id);
}
#Override
public User getUserWithGroups(long id) {
User user = (User) this.getCurrentSession().get(User.class, id);
Hibernate.initialize(user.getGroups());
return user;
}
#Override
public User getUserWithGroupsAndMessages(long id) {
User user = (User) this.getCurrentSession().get(User.class, id);
Hibernate.initialize(user.getGroups());
Hibernate.initialize(user.getMessages());
return user;
}
}
The point is I want to avoid creating multiple DAO methods for each combination of collections that must be loaded for each case. I'd like to achieve a call syntaxis in the Controller similar to User user = userService.getUser(id).initGroups().initMessages();, to chain only the specific methods I need in every case.
But in this particular example, the initXXX() methods would be in the model class User, which may not contain any #Autowired service, and because of that is not a possible solution.
Any ideas?
--EDIT--
Some non-working ideas:
Option 1. Declare initGroups() in User model class:
User
#Entity
#Table(name="user")
public class User implements Serializable {
//...
public User initGroups() {
Hibernate.initialize(getGroups());
return this;
}
}
Called from Controller this way: User user = userService.getUser(id).initGroups();
Option 2. Declare initGroups() in UserService and UserDAO:
UserServiceImpl
#Service
#Transactional
public class UserServiceImpl implements UserService {
//...
#Override
public User initGrupos(User user) {
return userDAO.initGroups(user);
}
}
UserDAOImpl
#Repository
public class UserDAOImpl implements UserDAO {
//...
#Override
public User initGroups(User user) {
Hibernate.initialize(user.getGroups());
return user;
}
}
Called from Controller this way:
User user = userService.getUser(id);
user = userService.initGroups(user);
Result (both the same):
org.hibernate.HibernateException: collection is not associated with any session
org.hibernate.collection.internal.AbstractPersistentCollection.forceInitialization(AbstractPersistentCollection.java:484)
org.hibernate.Hibernate.initialize(Hibernate.java:78)
...
The init methods don't need the service, they just need to call Hibernate.initialize while maintaining the previous Hibernate session which loaded the object in the first place (that is the most important part).
Related
First of all, I'm a relative noob to Spring Boot, so keep that in mind.
I've got a REST api in which I'm trying to minimize database calls for the same object and I've determined that using a Spring Bean scoped to the Request is what I want. Assuming that is correct, here is what I'm trying to do:
1) Controller takes in a validated PhotoImportCommandDto command
PhotoCommandController
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> importPhoto(#Valid #RequestBody PhotoImportCommandDto command){
...
}
2) PhotoImportCommandDto is validated. Note the custom #UserExistsConstraint which validates that the user exists in the database by calling a service method.
PhotoImportCommandDto
#Component
public class PhotoImportCommandDto extends BaseCommand {
#NotNull(message = "userId must not be null!")
#UserExistsConstraint
private Long userId;
...
}
What I would like to do is somehow set a Spring Bean of the user that is validated in the #UserExistsConstraint and reference it in various methods that might be called throughout a Http request, but I'm not really sure how to do that. Since I've never really created my own Spring Beans, I don't know how to proceed. I've read various guides like this, but am still lost in how to implement it in my code.
Any help/examples would be much appreciated.
You can use the #Bean annotation.
#Configuration
public class MyConfiguration {
#Bean({"validUser"})
public User validUser() {
User user;
//instantiate user either from DB or anywhere else
return user;
}
then you can obtain the validUser.
#Component
public class PhotoImportCommandDto extends BaseCommand {
#Autowired
#Qualifier("validUser")
private User validUser;
...
}
I don't really know how to make annotations in Java. Anyway, in Spring, checking where the User exists in the DataBase or not is one line of code:
userRepository.findOne(user) == null
That is accomplished by the Spring Data JPA project:
Create a JPA Entity User.
Set the spring.datasource.url and login/password in the
resources/application.properties.
Create this interface:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
}
Note, Spring implements it behind the scences.
Inject this interface into your RestController (or any other Spring bean):
private UserRepository userRepository ;
**constructor**(UserRepository ur){
userRepository = ur;
}
Note, a Spring Bean is any class annotated #Component (this includes stereotype annotations like Controller, Repository - just look up the contents of an annotation, it may use #Component internally) or returned from a method which is annotated #Bean (can only be on the Component or Configuration class). A Component is injected by searching the classpath, Bean is injected more naturally.
Also note, injecting is specifying #Autowired annotation on field or constructor, on a factory method, or on a setter. The documentation recommends that you inject required dependencies into constructor and non-required into the setter.
Also note, if you're injecting into a constructor and it is clean by the arguments, you may omit #Autowired annotation, Spring will figure it out.
Call its method findOne.
So, you can do one of the following:
Inject the userRepository into the #RestController constructor (as shown above). I would do that.
Inject the userRepository into the #Service (internally #Component) class that will do this sorts of thing for you. Maybe you can play with it to create an annotation.
p.s. Use #PostMapping instead of #RequestMapping(method = RequestMethod.POST)
p.p.s. If ever in doubt, go to the official documentation page and just press CTRL-F: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ Note the current word, that will always take you to the latest version.
p.p.p.s Each Spring project has its own .io webpage as well as quick Get Started Guides where you can quickly see the sample project with explanations expecting you to know nothing.
Hope that helps! :)
Don't forget to mark the answer as accepted if you wish
Using Jose's input, I took a bit of a different route.
Here's what I did:
I created a ValidatedUser class:
#RequestScope
#Component
public class ValidatedUser {
private UserEntity user;
public UserEntity getUser() {
return user;
}
public void setUser(UserEntity user) {
this.user = user;
}
}
and I also created a wrapper class HttpRequestScopeConfig to capture all variables to use over the course of an Http Request to the api.
#Component
public class HttpRequestScopeConfig {
#Autowired
private ValidatedUser validatedUser;
...
public UserEntity getValidatedUser() {
return validatedUser.getUser();
}
public void setValidatedUser(UserEntity validatedUser) {
this.validatedUser.setUser(validatedUser);
}
...
}
In my UserExistsConstraintValidator (which is the impl of #UserExistsConstraint, I set the validatedUser in the httpRequestScopeConfig:
public class UserExistsConstraintValidator implements ConstraintValidator<UserExistsConstraint, Long> {
//private Log log = LogFactory.getLog(EmailExistsConstraintValidator.class);
#Autowired
private UserCommandService svc;
#Autowired
private HttpRequestScopeConfig httpRequestScope;
#Override
public void initialize(UserExistsConstraint userId) {
}
#Override
public boolean isValid(Long userIdField, ConstraintValidatorContext context) {
try {
UserEntity user = svc.findUserOfAnyStatus((Long) userIdField);
if (user != null) {
httpRequestScope.setValidatedUser(user);
return true;
}
} catch (Exception e) {
//log.error(e);
}
return false;
}
}
Now, I can access these variables throughout the rest of my service layers by autowiring HttpRequestScopeConfig where necessary.
I want to implement a public/global variable so that I can access from any layer(controller/service/dao) of a spring project. For example
class Abc{
public User user;
public String subdomain;
}
Now I want to get user, subdomain values from any layer. But remember that, my project has user management. So I need to specific value for each user session.
Note:
The life cycle of this values is session
This is not singletone forall users
This is specific per session
Thanks
A possible solution is to have a service which has the ability to lookup the currently logged on user, and provides the context information you require. As #siledh mentioned, once you have this information, you can then pass it into layers where you do not want to have any concept of context (e.g. in your DOAs)
#Service
public class ContextService {
public User getLoggedOnUser() {
// Get user id/username from Spring Security principal
// Use id/username to lookup the User
// Return the logged on user
}
}
#Controller
public class SomeController {
#Autowired
private ContextService context;
#Autowired
private SomeReposity someReposity;
#RequestMapping("/home")
public String homePage() {
User loggedOn = contextService.getLoggedOnUser();
String userInformationNeededForHomePage =
someReposity.findSomethingForUser(loggedOn);
...
}
}
#Repository
public class SomeReposity {
public String findSomethingForUser(User user) {
// find something
}
}
The saveUser method doesn't save the user object name change when I have multiple operations inside one method. If I use #Transactional(propagation = Propagation.REQUIRED) on top of the saveUser service method, it works fine. When another class creates a new User object and sets all its values and calls the createUser method, it works fine. Why do I need #Transactional for the saveUser method? In what cases do I need to include #Transactional? I'm using Spring Data and JPA (Hibernate impl). Any ideas?
JPA Entity:
#Entity
public class User{
#Id
#GeneratedValue
private Long id;
#Column
private String name;
//getters/setters..etc
}
Spring Service:
#Service
public class UserServiceImpl{
#Autowired
UserRepository userRepository;
public void saveUser(Long id){
User user = userRepository.findById(id);
user.setName("newname");
userRepository.save(user);
}
public void createUser(User user){
userRepository.save(user);
}
}
Spring Data JPA/Hibernate Impl Repository:
public interface UserRepository extends JpaRepository<User, Long> {
}
The methods in JpaRepository are transactional by default (readonly for retrieving).
Now in your saveUser() method, you need #Transactional because you are retrieving an User by id and updating it and then again persisting to the database. Basically, #Transactional(readOnly=true) is used while reading else #Transactional is used in Spring Data JPA.
User user = userRepository.findById(id);
returns null to user if no user is found and user.setName("newname"); will give NullPointerException.
You need transactions if you update the database state (insert/update/delete) otherwise you'll end up having this behaviour.
Even if you do read-only operations in your methods, you should annotate them with #Transactional(readOnly=true) so Spring can optimize the transactional resource.
I'm following the Controller -> Service -> DAO pattern. When I call a DAO implementation, I get back a DTO/Data object. Which then gets passed to the service layer, bringing together it's respective business object and it's data object. Like so:
public User getUserById(int id) {
return new User(userDAO.getUserById(id));
}
class User {
private UserDTO userDTO;
public User(UserDTO userDTO) {
this.userDTO = userDTO;
}
}
What I'd like to do is wrap ALL my business logic inside the business class but I require additional dependencies.
For example, I'd like to be able to do something like this:
//... some code
User user = userService.getByUserId(1);
user.delete(); // this should delete the user from the database
In order for me to delete the user this way, I would need to Autowire the UserService into the business class but this will not work since I am manually instantiating the User class in the User Service.
class User {
#Autowired
private UserService userService; // this won't work since I call instantiate User myself, ie. new User()
private UserDTO userDTO;
public User(UserDTO userDTO) {
this.userDTO = userDTO;
}
public boolean delete() {
userService.deleteByUserId(userDTO.getId());
}
}
Is there a pattern I can follow to allow me to do what I want?
I don't think it's a good design to have business class as a member of your DTO objects.
A more proper approach would be to have your delete method in the business class. That would help loose coupling.
I think you can do this with the #Configurable annotation, though this really isn't how Spring is supposed to work. It will add lots of overhead to your application and make debugging harder.
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-atconfigurable
Default scope for a bean in spring is singleton. However when I have next service defined:
#Service("usersLoggedInService")
public class UsersLoggedInServiceImpl implements UsersLoggedInService {
private Map<Long, String> OnlineUsers = new LinkedHashMap<Long, String>();
#Override
public Map<Long, String> getOnlineUsers() {
return OnlineUsers;
}
#Override
public void setOnlineUsers(Long id, String username) {
OnlineUsers.put(id, username);
}
#Override
public void removeLoggedOutUser(Long id){
if(!OnlineUsers.isEmpty() && OnlineUsers.size()>0)
OnlineUsers.remove(id);
}
}
and using it for login auditing so whenever new user logged in I am adding it to OnlineUsers LinkedHashMap in next way:
usersLoggedInService.setOnlineUsers(user.getId(), user.getUsername());
in one of my service classes. This works fine and I can see the users added in map.
But, when on log out I want to remove the user added in LinkedhashMap and when I check usersLoggedInService.getOnlineUsers() I could see that its empty. I don't understand why.
Logout handler definition:
<logout invalidate-session="true"
logout-url="/logout.htm"
success-handler-ref="myLogoutHandler"/>
And its implementation:
#Component
public class MyLogoutHandler extends SimpleUrlLogoutSuccessHandler {
#Resource(name = "usersLoggedInService")
UsersLoggedInService usersLoggedInService;
#Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
if (authentication != null) {
Object principal = authentication.getPrincipal();
if(principal instanceof User){
User user = (User) principal;
usersLoggedInService.removeLoggedOutUser(user.getId());
}
}
setDefaultTargetUrl("/login");
super.onLogoutSuccess(request, response, authentication);
}
}
Please let me know where the problem is. I don't expect this map to be empty.
-----Updated ------
When new users logged in then I can see all the users already added in LinkedHashmap. This method is inside one of the user service class:
#Service("userService")
public class UserServiceImpl implements UserService {
#Autowired
UsersLoggedInService usersLoggedInService;
#Override
public User getUserDetail() {
Authentication auth = SecurityContextHolder.getContext()
.getAuthentication();
Object principal = auth.getPrincipal();
if(principal instanceof User){
User user = (User) principal;
usersLoggedInService.setOnlineUsers(user.getId(), user.getUsername());
return user;
}
return null;
}
}
when users logged in suppose two users logged in I get Map as {1=user1, 9=user2} but if any of the users logged out then inside onLogoutSuccess() method I get map value as {}. Now if one more user logged in then I get map {1=user1, 9=user2, 3=user3}. So,Map is getting empty inside onLogoutSuccess() method only and it showing populated values everywhere else.
From what you've described it looks like that new instance of service is created for handler.
It might be that default scope of your configuration is not singleton(it's should be easy to check).
Also, could you please try to use #Autowired annotation? There is subtle difference between #Autowire and #Resource from documentation it looks like it shouldn't cause such issue but worth to try anyway:
#Component
public class MyLogoutHandler extends SimpleUrlLogoutSuccessHandler {
#Autowired
private UsersLoggedInsService usersLoggedInService;
// ...
}
-----Update #1 ------
Yeap, try #Autowired with #Qualifier(when I tried that example without qualifier spring created two instances):
#Autowired
#Qualifier("usersLoggedInService")
UsersLoggedInService usersLoggedInService;
----Update #2 ------
Well, I simply copied all your code in sample project and it works on my machine.
Is it possible for you to share your codebase on service like GitHub?
Please show the class that is calling setOnlineUsers. And how and at what place did you check that map is not empty?
Try putting a logger in method onLogoutSuccess and check if you are getting a new or same instance of UsersLoggedInService.
And I am assuming you have a typo in the bean that you are wiring UsersLoggedIn*s*Service. There is an extra 's'