Using JPA Entity to persist HTTP Request - java

I expose an endpoint where my client invoke requests to fire spring batch jobs.
#RestController
#RequestMapping("/test")
public class TestController {
#Autowired
private MyProcessor processor;
#PostMapping(value = "/runJob", consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<MyResponse> runJob(#Valid #RequestBody MyRequest request) {
//persist the request using Spring JPA
String requestTrackingId = UUID.randomUUID().toString()
MyResponse response = processor.process(request, requestTrackingId); //run spring batch job and create response
return new ResponseEntity<>(response, CREATED);
}
}
Requests include name, and some other params:
public class MyRequest {
private String name;
private String runDate;
private boolean rerun;
}
I have a requirement to use Spring JPA to persist the http job run requests in a table. The table should persist the request data along with the unique id so it can be tracked and also should capture job status so it can be queried by clients using the tracking id.
I need help with the JPA implementation including creating the entity and persisting the request to the table when it hits the endpoint. What would the implementation look like? The id on the table should the tracking id.

Please give additional information if you want a more sophisticated answer.
One possible implementation:
The Model
#Entity
#Table
public class RequestData{
#Id
#Column(name = "id", nullable = false, unique = true, updatable = false, length = 32)
private String id; // = IdGenerator.generateId(); #use it if you want to get the id from the object, and not set it.
#Basic
private String name;
#Basic
private String runDate;
#Basic
private Boolean rerun;
//[getters/setters] here
}
Repository interface
public interface RequestDataRepository extends JpaRepository<RequestData, String>{}
Now you can inject the repostiory to your controller and save the request:
#Autowired
private RequestDataRepository repository;
#PostMapping(value = "/runJob", consumes = MediaType.APPLICATION_JSON_VALUE)
public HttpEntity<MyResponse> runJob(#Valid #RequestBody MyRequest request) {
String uniqueId=IdGenerator.generateId()
RequestData requestData=new RequestData();
requestData.setId(uniqueId);
requestData.setName(request.getName())
.... //other fields setters (except id)
repository.save(requestData)
//alternativelly get the id from the object, then you do not need to create id here, but in the RequestData object
//String uniqueId=requestData.getId();
[...]
}
UUID generator
public final class IdGenerator {
private static org.springframework.util.IdGenerator idGenerator = new AlternativeJdkIdGenerator();
private IdGenerator() {
throw new UnsupportedOperationException("IdGenerator is an utility class");
}
public static String generateId() {
return idGenerator.generateId().toString().replaceAll("-", "");
}
}

Related

Get values from User and update into mysql database in Springboot Controller

I'm developing a Banking system Project Where User Should be able to create an account in any available branch. Branch Creation will be maintained by admin. I had two tables(Entities) like BranchDetails and AccountDetails.
The BranchId Column is the primary key in BranchDetails and Foreign key in AccountDetails. Now When user Creates an account, he will input the Preferred Branch name and the AccountHolder name. I had to Insert this Account in AccountDetails Table which matching the branchId which the user had entered.
How do i achive this. So far i have tried,
BranchDetails.java
#Entity
#Table( name = "branchdetails")
public class BranchDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int branchId;
#Column( name = "branchName")
private String branchName;
#OneToOne(mappedBy = "branchdetails")
private AccountDetails accountDetails;
/getters and setters
}
AccountDetails.java
#Entity
#Table(name = "accountdetails")
public class AccountDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int customerId;
#Column(name = "customerName")
private String customerName;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "branchId")
private BranchDetails branchdetails;
/getters and setters
}
Controller
#Controller
public class ApiController{
#RequestMapping(value = "/branchcreation", method = RequestMethod.POST
,consumes = {"application/x-www-form-urlencoded"})
#ResponseBody
public String brCreation( BranchDetails br, String branchname){
br.setBranchName(branchname);
branchrepository.save(br);
return "Sucesspage";
}
#RequestMapping(value = "/accountcreation", method = RequestMethod.POST)
#ResponseBody
public String AcCreation(AccountDetails ad,BranchDetails br, String branchname,String customername){
br.setBranchName(branchname);
ad.setCustomerName(customername);
accountrepository.save(ad);
return "Sucesspage";
}
}
Prepare AcCreation method to receive branch id (the branches already exist, so you could send their ids and names to your frontend form's select component) and customer name (provided by the user in the input field):
It would look like this (I changed the name to createAccount, because it sounds naturally, there is no need to use shortcuts in method's name, but in the end it's your choice):
#RequestMapping(value = "/account-creation", method = RequestMethod.POST)
#ResponseBody
public String createAccount(String customerName, Integer branchId){
accountRepository.createAccount(customerName, branchId);
return "Sucesspage";
}
Look at the removed code from the service method.
Details connected with creation on the database should be contained by database access layer, in this case the class AccountRepository and database layer methods should be called by service's methods - in your case we left out the service class).
So you would create Account instance inside its method and then set the branchId field.
Or you could do something like this (you would have to have two separate repositories, one for AccountDetails, second for BranchDetails entities):
#RequestMapping(value = "/account-creation", method = RequestMethod.POST)
#ResponseBody
public String createAccount(String customerName, Integer branchId){
BranchDetails branchDetails = branchRepository.find(branchId);
AccountDetails accountDetails = new AccountDetails();
accountDetails.setCustomerName(customerName);
accountDetails.setBranchDetails(branchDetails);
accountRepository.save(accountDetails);
return "Sucesspage";
}

JPA MappedSuperClass common audit value for all entities

I have several JPA entities, each Entity has a database user column, in that column I have to store the user that makes changes to a specific row in the table.
I created a 'MappedSuperclass' Bean that all the entities would extend from, this is the MappedSuperclass.
#MappedSuperclass
public abstract class AuditableBean {
#Column(name = "modifier_user", nullable = false)
private String modifier_user;
// Getters and Setters
}
This is one Entity that extends from the 'MappedSuperclass'.
#Entity
#Table(name = "nm_area_ref")
public class ServingAreaReferenceBean extends AuditableBean {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "nm_area_ref_id")
private UUID id;
#Column(name = "nm_srv_area_desc", nullable = false)
private String description;
#Column(name = "nm_retired", nullable = false)
private boolean retired;
// Getters and Setters
}
And, all the Beans has a corresponding service method used to save the data on the database, this is one of the services class (each service injects a repository for CRUD operations).
// Service
#Component
public class ServingAreaReferenceBO {
#Inject private ServingAreaReferenceRepository repository; //repository injection
#Inject private CloudContextProvider cloudContextProvider;
public List<ServingAreaReferenceBean> getAllServingAreaReferences() {
return Lists.newArrayList(repository.findAll());
}
public Optional<ServingAreaReferenceBean> findById(UUID id) {
return repository.findById(id);
}
public ServingAreaReferenceBean create(ServingAreaReferenceBean servingAreaReference) {
Optional<CloudContext> cloudContext = Optional.ofNullable(cloudContextProvider.get());// line 1
servingAreaReference.setUpdaterUser(cloudContext.map(CloudContext::getUserId).orElse(null));// line 2
return repository.save(servingAreaReference);// line 3
}
}
// Repository - It extends from CrudRepository (insert, update, delete operations)
#Repository
public interface ServingAreaReferenceRepository extends CrudRepository<ServingAreaReferenceBean, UUID> {
boolean existsByDescription(String description);
boolean existsByDescriptionAndIdIsNot(String description, UUID id);
}
When 'repository.save()' (line 3) executes, It stores the user successfully, but I put the user logic just before executing the save method (lines 1, 2). So I don't think that repeating those two lines on each service would be the best approach, instead, I'd like to implement a generic method or a generic class that sets the user for all the Bean Entities before executing the save method.
Is that possible? what is the better approach for that?
I was thinking to implement something like this, but not sure how to make it generic?
#Component
public class AuditableBeanHandler {
#Inject private CloudContextProvider cloudContextProvider;
public AuditableBean populateAuditInformation(AuditableBean auditableBean) {
Optional<CloudContext> cloudContext = Optional.ofNullable(CloudContextProvider.get());
auditableBean.setUpdaterUser(CloudContext.map(cloudContext::getUserId).orElse(null));
return auditableBean;
}
}
Well what I understood, you have to set user before each save call of an entities :
This can be solved by using a well known design pattern called "Template method design pattern".
Just create a parent class for service class :
public abstract class AbstractService<T extends AuditableBean> {
public AuditableBean populateAuditInformation(AuditableBean auditableBean) {
Optional<CloudContext> cloudContext = Optional.ofNullable(CloudContextProvider.get());
auditableBean.setLastUpdatedByUser(CloudContext.map(cloudContext::getUserId).orElse(null));
return auditableBean;
}
public absract T save(T areaReference);
public final T create(T t) {
t = populateAuditInformation(t);
return save(t);
}
And in your service class extends this abstract service and add save method:
public class AreaReferenceService extends AbstractService<AreaReferenceBean> {
public AreaReferenceBean save(AreaReferenceBean AreaReference) {
return repository.save(AreaReference);
}
}
While calling service method, call create() method.
hope this will solve your problem, and also you can read more about Template method design pattern.
I think you're pretty close with the AuditableBean.
Add the following configuration to enable auditing:
// Annotate a configuration class with this
#EnableJpaAuditing(auditorAwareRef = "auditAware")
Add this "auditAware" bean, you'll want to tweak it to match whatever auth mechanism you're using. It's sole purpose is to return the username of the authenticated user, Spring will use this.
#Bean
public AuditorAware<String> auditAware() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Optional.of(authentication.getPrincipal());
};
}
Add one more annotation to your modified_user field (#LastModifiedBy). This tells Spring that when an update occurs on the entity, set this field to the value returned from your AuditAware bean.
#Column(name = "modifier_user", nullable = false)
#LastModifiedBy
private String modifier_user;
See the Spring Data JPA Documentation for more information on the available audit fields.

How do I insert data in a #JoinColumn column in Spring Boot?

When I pass data by POST request to my TodoItem model, only the columns specified by #column get filled in, but the #JoinColumn column is null even if I use the column name in the JSON I'm sending over. My GET request API work just fine however, so I omitted them from controller code.
TodoItem.java
#Entity
#Table(name = "todo_item")
public class TodoItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "todo")
private String todo;
#Column(name = "completed")
private boolean completed;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
public TodoItem() {
}
public TodoItem(String todo, boolean completed) {
this.todo = todo;
this.completed = completed;
}
// setters and getters
}
My constructor doesn't mention user_id, don't think it needs it though, but I may be wrong.
TodoController.java
#RestController
#RequestMapping("/api")
public class TodoController {
#Autowired
private UserRepository userRepository;
#Autowired
private TodoRepository todoRepository;
#PostMapping("/addItem")
public TodoItem addTodoItem(#RequestBody TodoItem todoItem) {
return this.todoRepository.save(todoItem);
}
}
I send POST to http://localhost:8080/api/addItem
Request:
{
"todo": "finish portfolio",
"completed": false,
"user_id": 1
}
However in my MySQL workbench, todo and completed get populated properly but user_id says null
In spring data (using hibernate or JPA), when saving entity which has reference to another entity . you must first fetch the referenced object first by id and then set it using setter in persistence entity. after that you can save and get (FK column) to be saved.
for example you first must use user repository and call
User user = userRepository.findById(userId);
and then
todoItem.setUser(user);
after that you can save item and FK column will get populated.
I think this the way to save reference entity. you can not relay on int id only.
also your request body JSON must be like this :
{
"todo": "finish portfolio",
"completed": false,
"user": {
"user_id":1
}
}
also it best practice to define and use DTO object instead of entity itself.

In spring boot JPA, how to properly POST an object whose entity representation has a foreign key association to a different entity?

If I have a entity that contains an object of an another class, for example a Book entity that has within it a Publisher entity that is associated as follows:
#ManyToOne
#JoinColumn(name="PUB_CODE", referencedColumnName = "PUB_CODE")
private Publisher pub;
Is this a secure/correct (I saw the correct data in the DB in this example, but not 100% sure if it would work in all cases) approach to post an object that has foreign key association in the database? I don't know if this is safe to do in terms of transaction atomicity or in terms of threading, or if it is efficient. Relevant code below:
Book.java
package app.domain;
/*imports*/
#Entity
public class Book implements Serializable{
/**
*
*/
private static final long serialVersionUID = -6902184723423514234L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(nullable = false, unique=true)
private String bookName;
#Column(nullable = false)
private int pageCount;
#ManyToOne
#JoinColumn(name="PUB_CODE", referencedColumnName="PUB_CODE")
private Publisher pub;
/*public getters and setters*/
}
Publisher.java
package app.domain;
/*imports*/
#Entity
public class Publisher implements Serializable {
private static final long serialVersionUID = 4750079787174869458L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="PUB_CODE",nullable = false, unique = true)
private String publisherCode;
#Column(nullable = false)
private String publisherName;
/*public getters and setters*/
}
BookRepo.java
package app.service;
/*imports*/
public interface BookRepo extends JpaRepository<Book, Long>{
#Query("SELECT pb FROM Publisher pb WHERE pb.publisherCode = TRIM(UPPER(:pubCode))")
public Publisher findPublisherByPubCode(#Param("pubCode")String pubCode);
}
BookController.java
package app.controller;
/*imports*/
#RestController
#RequestMapping(value = "/books")
public class BookController {
private BookRepo bookRepo;
#Autowired
public BookController(BookRepo bookRepo) {
this.bookRepo = bookRepo;
}
//The ApiPathParam is for JSONDOC purposes
#RequestMapping(value = "/create", method = RequestMethod.POST)
public List<Book> create(#ApiPathParam(name = "book") #RequestBody Book book, #ApiPathParam(name = "pubCode") #RequestParam("pubCode") String pubCode) {
// Assume exception handling
Publisher pbToAttachToThisBook = bookRepo.findPublisherByPubCode(pubCode);
book.setPub(pbToAttachToThisBook);
bookRepo.save(book);
return bookRepo.findAll();
}
}
Post object body (input into a POST tool):
{
"bookName": "goosebumps",
"id": 0,
"pageCount": 332,
"pub": {
"id": 0,
"publisherCode": "",
"publisherName": "",
"serialVersionUID": 0
},
"serialVersionUID": 0
}
pubCode parameter input provided, also into the POST tool, in the same call as above: 'SC'
After the above code was executed, in the Book table, there was an entry for the book above, with its PUB_CODE foreign key column filled in with 'SC', and the returned List<Book> of the POST controller method that was called showed that the newly added book included the Publisher entity information (such as the full name "Scholastic") for publisher with PUB_CODE='SC' that was already existing in the database.
Thank you.
The technique you posted originally (passing the FK ID, retrieving it manually in your controller, and setting it on the entity explicitly) is valid and secure.
I don't know of a cleaner approach unless you move to HATEOAS principals, which allows for resource link handling: http://projects.spring.io/spring-hateoas/
Sounds like you need to separate/decouple your data layer's domain model from your Rest Resources/ API specs, as they could evolve at a different pace. Also your choice of JPA should not influence the API specs.
Should this feel like something you want to pursue there lots of resources out there including Best Practices for Better RESTful API

Hibernate failed to lazily initialize a collection of role no session in entity service even with #Transactional

I have a hibernate project that connects to two data sources and has a base dao. I use the base dao to derive generic daos for the data sources as well.
I have a service that fetches a join relationship and I am running into the following error when ever an entity has a one to many relationship. I am stuck in that I am not able to get the one to many relationship objects.
I used the apache commons beanutils to copy properties hoping it would do a deep copy but it does not work either. I am running out of ideas.
I read that I there is the option to use Open Session In View but I am not sure how to go about that.
Error
failed to lazily initialize a collection of role: com.sony.spe.mc.domain.OperationalUnit.user, could not initialize proxy - no Session
I have the following layers and files
Controller Layer
#ApiOperation(value = "read", nickname = "getByID")
#RequestMapping(value="/read", method = RequestMethod.GET)
#ApiImplicitParams({
#ApiImplicitParam(name = "id", value = "User's id", required = true, dataType = "int", paramType = "query")
})
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Success", response = User.class),
#ApiResponse(code = 500, message = "Failure")})
#ResponseBody
public UserProxy getByID(long id) {
UserProxy user;
try {
user = userService.fetchUserById(id);
}
catch(Exception ex) {
ex.printStackTrace();
return null;
}
return user;
}
The Service Layer
User Service
#Service
#Transactional("fuseTransactionManager")
public class UserServiceImpl extends BaseFuseServiceImpl<User> implements UserService {
#Autowired
UserDao userDao;
#Override
protected UserDao getDao() {
return userDao;
}
#Override
public UserProxy fetchUserById(long id) {
try {
/***** ERROR HAPPENS HERE. THE USER OBJECT IS NOT ABLE TO GET THE ONE TO MANY OBJECTS even though I have #Transactional *****/
User user = userDao.fetchEntityById(User.class, id);
UserProxy userProxy = new UserProxy();
BeanUtils.copyProperties(userProxy, user);
return userProxy;
} catch(Exception ex) {
ex.printStackTrace();
return null;
}
}
.....
Generic Fuse Service
#Service
public class BaseFuseServiceImpl<T extends Serializable> extends BaseServiceImpl<T> implements BaseFuseService<T> {
#Autowired
BaseFuseDao<T> baseFuseDao;
#Override
protected BaseFuseDao<T> getDao() {
return baseFuseDao;
}
}
Very generic service that the specific services implement
#Service
#Transactional("baseTransactionManager")
public abstract class BaseServiceImpl<T extends Serializable> implements BaseService<T> {
protected abstract BaseDao<T> getDao();
#Override
public T fetchEntityById(Class<T> entityClass, long id) {
return getDao().fetchEntityById(entityClass, id);
}
User Entity. There is also a corresponding proxy object that is pretty much identical except it does not have the #Entity #Column annotations.
#Entity
#Table(name="USER")
//#AttributeOverride(name = "ID", column = #Column(name = "USER_ID", nullable = false))
public class User extends BaseEntity {
//public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 5236507646361562577L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "USER_ID")
private long id;
#OneToMany(cascade=CascadeType.ALL)
#JoinTable(name="USER_OPERATIONALUNIT",
joinColumns = {#JoinColumn(name="USER_ID")},
inverseJoinColumns = {#JoinColumn(name="OPERATIONALUNIT_ID")}
)
private Set<OperationalUnit> operationalUnit;
.... getters and setters
Why you want make all the methods #Transactional inside class. You can use #Transactional whenever it requires. For best practices use #Transactional over the method.
So in Your case there are 2 solutions:
1.By Fetching childs eagerly:
You need to pass fetchType Eagerly with your annotation i.e.
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
2. By using #Transactional:
Here you need to initialize you collection in current transaction i.e.
public UserProxy fetchUserById(long id) {
try {
User user = userDao.fetchEntityById(User.class, id);
// Here you need to initialize collection using getter method after retrieving object
user.getOperationalUnit().size();
//Now you will get childs with your parent entity
UserProxy userProxy = new UserProxy();
BeanUtils.copyProperties(userProxy, user);
return userProxy;
} catch(Exception ex) {
ex.printStackTrace();
return null;
}
}
For more details about #Transactional Refer this link.

Categories