I'm new at Java and trying to understand how MVC architecture goes. Forgive me if I'm wasting your time. I wrote a DAO Service, it handles the crud model (get, read, update, delete).
public List<User> getUsers();
public User getUser(Long userId);
public void createUser(User user);
public void updateUser(User user);
public void delete(Long userId);
}
here are my abstract DAO functions.
#Override
#Transactional
public void updateUser(User user) {
em.merge(user);
}
and the controller:
#PutMapping(value = "/{userId}", produces = "application/json")
public ResponseEntity<UserDTO> update(#PathVariable Long userId, #RequestBody UserDTO user){
try{
service.updateUser(user);
return new ResponseEntity<>(HttpStatus.OK);
} catch (HttpClientErrorException p){
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} catch (HttpServerErrorException.InternalServerError u){
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
in the service:
#Override
public void updateUser(UserDTO user) {
userDAO.updateUser(ApiDTOBuilder.userDTOToUser(user));
}
How can I pass the userId and set the new parameters into the current user object?
First you need to fetch the User using DAO method. And then you need to set the values to the User entity. As you are using #Transactional, spring will take care of updating the values.
#Override
#Transactional
public void updateUser(Long userId,UserDTO userdto) {
User user= getUser(userId);
user.setFirstName(userdto.getFirstName());
user.setLastName(userdto.getLastName())
}
Also if you are not using Spring Boot, then you need to enable transaction management by using
#EnableTransactionManagement
Related
I have a REST Controller, which is secured and needs to be tested:
#RestController
#RequestMapping("/account")
public class ViewerEditorEndpoint {
private final ViewerEditorService viewerEditorService;
public ViewerEditorEndpoint(ViewerEditorService viewerEditorService) {
this.viewerEditorService = viewerEditorService;
}
#PutMapping("/{username}")
#Secured({"ROLE_VOLUNTEER", "ROLE_INDIVIDUAL", "ROLE_COMPANY"})
UserDTO editAccountInfo(#PathVariable String username, #RequestBody UserDTO userDTO) {
Optional<UserDTO> oUser= viewerEditorService.editAccountInfo(username, userDTO);
return oUser.orElse(null);
}
#GetMapping("/{username}")
//#Secured({"ROLE_VOLUNTEER", "ROLE_INDIVIDUAL", "ROLE_COMPANY"})
UserDTO getAccountInfo(#PathVariable String username) {
if (username.equals(principal.getName())) {
return viewerEditorService.getAccountInfo(username).orElse(null);
}
return null;
}
}
I tried it with #WithMockUser, #WithUserDetails, but none really works. I saw a lot of examples with Mockmvc but I don't really understand how to implement this. I would appreciate some tipps(:
Knowing that there are some frameworks to do it in real-world situation. As I learning from scratch, I wonder is that possible to create pure MongoDB DAO as a layer on top of the Service class to do CRUD operation that also facilitate other DAO use it?
For example, below is my Generic DAO class to operate CRUD process.
public interface IGenericDAO<T> {
public T create(T t);
public T update(T t);
public T get(Object id);
public void delete(Object id);
public List<T> listAll();
public Long count();
}
Then, DAO class should implements its operations
public class UserDAO implements IGenericDAO<User> {
MongoDatabase database = dBUtils.getMongoDB();
MongoCollection <Document> userTbl = database.getCollection("User");
public UserDAO() {
super();
}
#Override
public User create(User user) {
return user;
}
// other CURD below
}
User Service class
public class UserService {
private UserDAO userDAO;
public UserService() {
}
public void listUser() {
// need to test
List<User> listUsers = userDAO.listAll();
}
public void create(User user) {
// this what I want to see. user is saved to db here
try {
MongoDatabase database = dBUtils.getMongoDB();
assert mdb != null;
// create or get collection
MongoCollection<User> userTbl = database.getCollection("Users", User.class);
User userDoc = new User();
userTbl.insertOne(userDoc);
} catch (Exception e) {
e.printStackTrace();
}
}
Having said that, I want to put MongoDB "layer" between the DAO and Service class to do the CRUD operation. I wonder is it necessary, otherwise, how to do it to help the UserService with UserDAO class?
I want to pass the user object I use for authentication in a filter to the resource. Is it possible?
I'm using wildfly 10 (resteasy 3)
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Inject
private UserDao userDao;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
logger.warn("Filter");
String uid = requestContext.getHeaderString("Authorization");
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private User validateUser(String uid) {
return userDao.getById(uid);
}
}
There are two ways I could see to do this. The first is, perhaps, the more standard way but is also more code. Ultimately you'll inject the user as part of the request. However, the first thing you need for this solution is a Principal. A very simple one might be:
import java.security.Principal;
...
public class UserPrinicipal implements Prinicipal {
// most of your existing User class but needs to override getName()
}
Then, in your filter:
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return user;
}
#Override
public boolean isUserInRole(String role) {
// whatever works here for your environment
}
#Override
public boolean isSecure() {
return containerRequestContext.getUriInfo().getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
// again, whatever works
}
});
In the class where you want the User, you could do something like:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething() {
User user = (User)securityContext.getUserPrincipal();
}
}
I've implemented it this way and it works pretty well. However, an arguably simpler way is to just store the user in the session:
#Context
private HttpServletRequest request;
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
request.getSession().setAttribute("user", user);
Then, in your service:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething(#Context HttpServletRequest request) {
User user = (User)request.getSession().getAttribute("user");
}
}
The downside of the second method is that you are really no longer a stateless service as you're storing state somewhere. But the HttpSession is there even if you don't use it.
In my application, I have a service layer, which is many REST web services that deal with the DAO layer to do CRUD operations on the entities which represent database tables. I am using Hibernate in my DAO layer. An example of my Service layer Java classes was:
#Path("/customers")
public class CustomerService extends SessionUtil implements Service {
public static CustomerDao customerDao = (CustomerDao) context.getBean("customerDAO");
public static CustomerDebtDao customerDebtDao = (CustomerDebtDao) context.getBean("customerDebtDAO");
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Customer> getAllCustomersService() {
return (ArrayList<Customer>) customerDao.getAllCustomers();
}
#GET
#Path("/{start}/{end}")
#Produces(MediaType.APPLICATION_JSON)
public List<Customer> getCustomerBatchService(#PathParam("start") int start, #PathParam("end") int end) {
ArrayList<Customer> customers = (ArrayList<Customer>) customerDao.getCustomerBatch(start, end);
return customers;
}
#GET
#Path("/count")
#Produces(MediaType.APPLICATION_JSON)
public int getTotalRowCountService() {
return (int) customerDao.getTotalRowCount();
}
#GET
#Path("/{customerId}")
#Produces(MediaType.APPLICATION_JSON)
public Customer getCustomerService(#PathParam("customerId") int customerId) {
Customer customer = (Customer) customerDao.getCustomer(customerId);
return customer;
}
#POST
#Path("/create")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response createCustomerService(Customer customer) {
customerDao.createCustomer(customer);
return response;
}
#DELETE
#Path("/{customerId}")
#Produces(MediaType.APPLICATION_JSON)
public Response deleteCustomerService(#PathParam("customerId") int customerId) {
customerDao.deleteCustomer(customerId);
return response;
}
#PUT
#Path("/{customerId}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response editCustomerService(Customer customer) {
customerDao.editCustomer(customer);
return response;
}
...
}
An example of the DAO layer is:
public class CustomerDaoImpl extends JdbcDaoSupport implements CustomerDao {
#Autowired
private SessionFactory sessionFactory;
#Override
public void createCustomer(Customer customer) {
customer.setCustomerId(getNextCustomerId());
customer.setCreated(new Date());
customer.setCustomerId(getNextCustomerId());
Session session = sessionFactory.openSession();
session.beginTransaction();
session.save(customer);
try {
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
#Override
public void editCustomer(Customer customer) {
Session session = sessionFactory.openSession();
session.beginTransaction();
session.update(customer);
try {
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
}
The problem is, in the service layer, I sometimes make more than one call to the DAO layer, and as you see, each call is handled in one Hibernate transaction which causes that if one operation fails, the others won't be executed. For example, I am telling the code to create an invoice then update the debts of the customer. I could find that it created an invoice and did not update debts. I looked into a couple of books and they all said I should handle all operations in a single transaction and roll them back if anything fails. I am trying to do this, but it's causing me to almost remove the whole DAO layer, and the service layer is getting huge, unreadable and unmaintainable. An example is as follows:
#POST
#Path("/create")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response createSalesInvoiceLineService(SalesInvoiceLine salesInvoiceLine) {
Session session = sessionFactory.openSession();
session.beginTransaction();
// prepare sales invoice line object
salesInvoiceLine = salesInvoiceLineDao.createSalesInvoiceLine(salesInvoiceLine);
session.save(salesInvoiceLine);
updateSalesInvoiceAmountForCreate(salesInvoiceLine, session);
// update stock
Stock stock = stockDao.getStockByProduct(salesInvoiceLine.getProductId());
stock.setQuantity(stock.getQuantity().subtract(salesInvoiceLine.getQuantity()));
stockDao.editStock(stock);
session.save(stock);
// update debt
SalesInvoice salesInvoice = salesInvoiceDao.getSalesInvoice(salesInvoiceLine.getSalesInvoiceId(), session);
List<CustomerDebt> customerDebtList = customerDebtDao.getCustomerDebtByCustomerId(salesInvoice.getCustomerId());
CustomerDebt customerDebt = customerDebtList.get(0);
customerDebt.setAmount(customerDebt.getAmount().add(salesInvoiceLine.getLineAmount()));
Date date = new Date();
java.sql.Date currentDate = new java.sql.Date(date.getTime());
customerDebt.setUpdateDate(currentDate);
customerDebtDao.editCustomerDebt(customerDebt);
session.update(customerDebt);
try {
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
session.close();
}
return response;
}
in addition to existing comments:
add pure service layer where you implement business logic using DAOs and use that layer via dependency injection in REST layer e.g.
#Path("/customers")
public class CustomerService {
private Service service;
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Customer> getAllCustomersService() {
return (ArrayList<Customer>) service.getAllCustomers();
}
...
service layer can be easily tested, exposed to other transport layers(SOAP,..)...
I've encountered a problem I don't really understand - unless a method working with the DAO is annotated as #Transactional, the underlying database doesn't get updated. My app runs on JPA/Hibernate, Spring and Wicket. Why is that?
DAO:
#Repository(value = "userDao")
public class UserDaoJpa implements UserDao {
#PersistenceContext
private EntityManager em;
public User findById(Long id) {
return em.find(User.class, id);
}
public List findAll() {
Query query = em.createQuery("select e from User e");
return query.getResultList();
}
public User create(User user) {
em.persist(user);
return user;
}
public User update(User user) {
return em.merge(user);
}
public void delete(User user) {
user = em.merge(user);
em.remove(user);
}
}
Service:
#Service(value = "userManager")
public class UserManagerImpl implements UserManager {
#Autowired
UserDao dao;
public void setUserDao(UserDao dao) {
this.dao = dao;
}
public List getUsers() {
return dao.findAll();
}
public User getUser(String userId) {
return dao.findById(Long.valueOf(userId));
}
public void saveUser(User user) {
dao.update(user);
}
#Transactional
public void removeUser(User user) {
dao.delete(user);
}
}
In case I leave out the #Transactional annotation, the database doesn't get updated.
Well thats normal:
Every database manipulation in the CRUD Scheme needs it's transaction boundaries. Without those boundaries, nothing gets actually written in the DB.
A Transaction is a collection of DB Manipulations (inserts, Updates) whoich have all to be successfull or the whole action gets undone by the DB. That's why you have to tell Hibernate when a Transaction starts and ends, so hibernate can tell which actions have to be regardes as Units of Work. Without transaction boundaries the final commit to the database never happens.
hope that helped
Try to flush and commit.