I'm not fully understanding when to use #SessionAttributes vs #Scope("session") beans.
Currently, I'm doing the following
#ControllerAdvice(assignableTypes = {DashboardController.class, FindingWholeSalersController.class})
public class AuthControllerAdvice {
private IFindWholeSalerService service;
public IFindWholeSalerService getService() {
return service;
}
#Autowired
public void setService(IFindWholeSalerService service) {
this.service = service;
}
//put firstname in session etc..
#ModelAttribute
public void addWholesalerDiscoveryCountToSession(Model model, Principal principal){
if (!model.containsAttribute("firstname")) {
String firstname = service
.findUserFirstName(principal.getName());
model.addAttribute("firstname",
firstname);
}
}
Notice this if test if (!model.containsAttribute("firstname"))
Basically, if the session attribute is already in the model, then I dont want to ask my service layer to make a database request. However, every #RequestMapping call in any of the controllers I'm advising, first makes a call to
#ModelAttribute
public void addWholesalerDiscoveryCountToSession(Model model, Principal principal)
Does the if test, and moves on its marry way.
Is this the right solution for keeping data in the session so you dont have to call your database, OR would #Scope("session") beans be a better choice OR something else?
Thanks for all advice in advance!
Related
Suppose I develop a ticket order web service. There are some steps to order a ticket and need to keep some users data between the steps.
Suppose I use Spring (Boot) technology stack and MVC
How is better to implement it?
Use stateless REST and move the date back and forth from step to step using cookies?
Store it in session context?
Use stateful beans (what are they like in Spring? Prototype? )
Use some stateful protocol, like SOAP (is it stateful?)
It depends.
1 If you want to use multiple instances of your web service (for balance load, etc) then your choice is a stateless REST and token-based authentication
2 If you don't need this functionality you can store your session information in MVC Model (It will put it in session, anyway)
#RestController
#SessionAttributes("armUserSession")
public class SessionController {
#Autowired
private LoginService loginService;
#ModelAttribute("armUserSession")
public ArmUserSession getArmUserSession() {
return new ArmUserSession();
}
#CrossOrigin
#RequestMapping({"/login"})
public ArmUserSession login(#ModelAttribute("armUserSession") ArmUserSession userSession,
Model model,
#RequestParam(required = false) String login,
#RequestParam(required = false) String password) {
if (!userSession.isLoggedIn()) {
userSession = loginService.login(login, password);
model.addAttribute("armUserSession", userSession);
}
return userSession;
}
#CrossOrigin
#RequestMapping({"/logout"})
public ArmUserSession logout(SessionStatus status) {
status.setComplete();
return new ArmUserSession();
}
}
3 You can use session scoped beans too, but it is a little more complicated.
By default Spring beans are singletons. When you want to use session scoped bean (they are not singletons) in singleton your need a proxy.
#Service
public class LoginServiceImpl implements LoginService {
#Autowired
private ArmUserSessionProxy armUserSessionProxy;
#Override
public ArmUserSession login(String login, String password) {
ArmUserSession armUserSession = armUserSessionProxy.getArmUserSession();
...................................
}
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ArmUserSessionProxy {
private ArmUserSession armUserSession = new ArmUserSession();
public ArmUserSession getArmUserSession() {
return armUserSession;
}
}
I have a very basic Spring Boot/JPA stack app, with a controller, service layer, and repository that does not persist updates as I understand it should.
A trivial Entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
protected Customer() {}
public Customer(String name) { this.name = name; }
// standard getters,setters //
}
A trivial Repository:
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {}
A simple Service layer:
// If the service is #Transactional and the controller is not, the update does NOT occur
#Transactional
#Service
public class CustomerService {
private static final Logger LOG = getLogger(CustomerService.class);
#Autowired
private CustomerRepository customerRepository;
boolean updateCustomerName(Long id, String name) {
Customer customer = customerRepository.findOne(id);
if (customer == null) { return false; }
// Modifies the entity
customer.setName(name);
// No explicit save()
return true;
}
}
And a REST controller that uses it all:
// If the controller is #Transactional and the service is not, the update occurs
#RestController
#RequestMapping("/mvc")
public class CustomerController {
#Autowired
private CustomerService customerService;
#RequestMapping(path = "{id}", method = RequestMethod.PUT)
public ResponseEntity updateCustomerName(#PathVariable Long id, #RequestParam("name") String name) {
customerService.updateCustomerName(id,name);
return ResponseEntity.noContent().build();
}
}
These are wired together with a simple one-liner SpringBootApplication
I have SQL debug logs enabled and see the selects, update, etc.
With the code above: When the service method is invoked by the controller, the modified entity is not persisted. SQL logs show the select of the entity but no update.
There is also no update if nothing is marked #Transactional
However, simply by moving the #Transactional annotation from the service class to the controller class, the SQL update does occur.
If I add an explicit customerRepository.save(customer) to the service method, the update also occurs. But my understanding is that the ORM should automatically save modified persistent entities.
I'm sure the issue has something to do with the EntityManager lifecycle in the web request, but I'm puzzled. Do I need to do additional configuration?
Complete example at https://github.com/monztech/SO-41515160
EDIT: This was solved, see below. Per the Spring spec #Transactional does not work in package-private methods and mistakenly did not make the update service method public.
The update will occur if the method is public and the service class has the #Transactional annotation.
I do have another question, however. Why is the #Transactional annotation necessary? (the update does not occur without it) Shouldn't the entity manager still persist the object because of the open session in view mechanism that Spring uses, independent of any transaction?
Make your updateCustomerName method public.
I'm doing an Spring MVC webapp, then I want to send an email to new registered user in confirm / activation purposes. Then I write an ApplicationEvent and ApplicationListener<> classes. The problem is that, listener is going to invoke twice, so I receive 2 emails, and have 2 token strings in my database.
How to solve this problem?
Code below,
ApplicationEvent:
public class OnRegistrationSuccessEvent extends ApplicationEvent {
private String appUrl;
private Locale locale;
private User user;
public OnRegistrationSuccessEvent(String appUrl, Locale locale, User user) {
super(user);
this.appUrl = appUrl;
this.locale = locale;
this.user = user;
}
// getters, setters
}
ApplicationListener:
#Component
public class RegistrationListener implements ApplicationListener<OnRegistrationSuccessEvent> {
#Autowired
UserService userService;
#Autowired
MessageSource messages;
#Autowired
JavaMailSender javaMailSender;
#Override
public void onApplicationEvent(OnRegistrationSuccessEvent event) {
this.confirmationEmail(event);
}
private void confirmationEmail(OnRegistrationSuccessEvent event){
// register token in DB, send mail
}
private SimpleMailMessage buildEmailMessage(OnRegistrationSuccessEvent event, User user, String token) {
//build some message
}
}
I invoke the event in #Controller with:
eventPublisher.publishEvent(new OnRegistrationSuccessEvent(URL, request.getLocale(), user));
Thank you for help,
Assuming publishEvent is ApplicationEventPublisher#publishEvent with an implementation from one of the built-in ApplicationContext subtypes, then it will invoke onApplicationEvent only once.
What I think you are experiencing is having multiple RegistrationListener objects. An ApplicationContext will propagate any events that are published to it to its parent context. I believe you have a RegistrationListener bean in each of your root and servlet application contexts. Look at your configuration to figure out how that happened. (You can also log this (or something uniquely representative of an instance) within RegistrationListener to confirm this.)
How to solve this problem?
Change your configuration to only produce and register one Listener across all your ApplicationContexts.
I have rest style controller in Spring. In controller I have injected dao interfaces. From controller I persist data. In the other words, I have like REST web service. people sends me data, and I persits it.
/**
* Payment rest controller which receives
* JSON of data
*/
#Controller
#RequestMapping("/data")
public class PaymentTransaction {
#Autowired
private TestDao dao;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
}
At the moment I have #transaction annotation in Dao classes. For instance:
import org.springframework.transaction.annotation.Transactional;
#Component
#Transactional
public interface TestDao {
#Transactional(propagation = Propagation.REQUIRED)
public void first();
}
I have read that this is very bad style. Using this answer at stackoverflow , here is explain and examples why is this bad - we must not add this annotation in DAO and in controller too. We must add it in service layer.
But I don't understand what is the service layer? Or where is it? I do not have anything like this.
where should I write #Transactional annotation?
Best regards,
According to the cited post, you should design your classes somehow like this (rather pseudocode):
controller (responsible for handling clients' requests/responses)
#Controller
#RequestMapping("/data")
public class TestREST {
#Autowired
private TestService service;
public void storePayment(PaymentDTO dto) {
service.storePayment(dto); //request from a client
}
public PaymentDTO getPayment(int paymentId) {
return service.getPayment(paymentId); //response to a client
}
}
service layer (also called business layer, responsible for business logic - knows what to do with incoming messages, but does not know where they come from).
public class TestServiceImpl {
#Autowired
private TestDao dao;
#Transactional(propagation=Propagation.REQUIRED) //force transaction
public void storePayment(PaymentDTO paymentDto) {
// transform dto -> entity
dao.storePayment(paymentEntity); //read-write hence transaction is on
}
#Transactional(propagation=Propagation.NOT_SUPPORTED) //avoid transaction
public Payment getPayment(int paymentId) {
return dao.findPayment(paymentId); //read-only hence no transaction
}
}
data access layer (also called persistence layer, responsible for accessing database - knows how to use entity model / ORM, does not know anything about the upper service layer)
public class TestDAOImpl {
#PersistenceContext
private EntityManager em;
public void storePayment(PaymentEntity paymentEntity) {
em.persist(paymentEntity);
}
public PaymentEntity getPayment(int paymentId) {
return em.find(PaymentEntity.class, paymentId);
}
}
By this approach you get separation of concerns mentioned in the post. From the other hand such an approach (business layer vs data access layer) got a little dose of criticism from Adam Bien's on his blog ("JPA/EJB3 killed the DAO"). As you can see there is no a single solution for the problem, but I encourage to read some other opinions and apply the solution you find the most suitable for your needs.
When you call two Dao methods first & second from controller, 2 transactions will be done, one with starts before first method and ends after it's execution and the second one starts before second method starts and ends after it's execution. Whereas you create an additional class in between controller and dao (usually this is called service layer) and annotate it with #Transactional and call multiple Dao methods in it, a transaction is started at the start of service method and all the dao calls will be executed and transaction will be closed, which is what you require. And inject the Service into Controller.
Controller -> Service -> Dao
#Controller
#RequestMapping("/data")
public class PaymentTransaction {
#Autowired
private TestService service;
#RequestMapping(value = "/test", method = RequestMethod.POST)
#ResponseBody()
public String test(HttpServletRequest request) {
...
}
}
#Service
#Transactional
public class TestService {
#Autowired
private TestDao dao;
#Transactional
public void serviceCall(){
dao.first();
dao.second();
}
}
I'm very new to Spring and I'm encountering the following problem.
I've got the following Controller, in which the #Autowired works perfectly (tried debugging and it works fine).
#Controller
#RequestMapping(value = "/registration")
#SessionAttributes("rf")
public class RegistrationController
{
#Autowired
UserJpaDao userDao;
#RequestMapping(method = RequestMethod.GET)
#Transactional
public String setupForm(Model model) throws Exception
{
model.addAttribute("rf", new RegistrationForm());
return "registration";
}
#RequestMapping(method = RequestMethod.POST)
#Transactional
public String submitForm(#ModelAttribute("rf") RegistrationForm rf, Model model) throws Exception
{
// ...
User user = rf.getUser();
userDao.save(user);
// ...
return "registration";
}
}
But when I submit my form, the #Autowired field in my RegistrationForm remains null.
RegistrationForm.java:
#Component
public class RegistrationForm
{
#Autowired
CountryJpaDao countryDao;
// ... fields...
public RegistrationForm()
{
}
#Transactional
public User getUser() throws InvalidUserDataException
{
//...
Country c = countryDao.findByCode("GB"); // Throws java.lang.NullPointerException
// ...
}
// ... getters/setters...
}
Here is the form's HTML/JSTL:
<form:form method="POST" modelAttribute="rf">
...
</form:form>
Can anyone help me?
Thank you.
(inspired by this post on SpringSource forums)
You're mixing up your concepts here. You use the likes of #Component and #Autowired for Spring-managed beans, and #ModelAttribute for transient, throwaway objects that are used to bind form data. The two should not be mixed. Your #Component and #Autowired annotations on RegistrationForm will be ignored by Spring, because they're not appropriate in that context.
Classes like RegistrationForm should represent the form data, and nothing else. Typically, the controller would ask RegistrationForm for the user ID, and would then look at the actual User object from the DAO itself. If you want RegistrationForm to look up the User itself, then your controller needs to manually supply the DAO to RegistrationForm when it asks for the User object.
As far as that post on the Spring forum is concerned, you'll notice that it never received an answer. It's not a good source to take inspiration from.
Note that I'm not saying that desiring to autowire beans into a form back object is a bad idea, I'm just saying that Spring doesn't do that.
It would work if you use the #Configurable annotation on your model, and this aspectJ configuration on your gradle file:
compileJava << {
ant.taskdef(
resource: 'org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties',
classpath: configurations.compile.asPath)
ant.iajc(
inpath: sourceSets.main.output.classesDir.absolutePath,
classpath: configurations.compile.asPath,
aspectPath: configurations.aspects.asPath,
destDir: sourceSets.main.output.classesDir.absolutePath
)
}
In this way aspectJ will generate code that does the auto wiring.
#Configurable
public class RegistrationForm
{
...
}