I have two projects, but they are not using the same DAO layer, and one project use an URL request to invoke the methods from the other project.
I encountered a problem, when I create an entity, I have to create separated DAO, service and so on. This DAO is use different fragement.
I want to find one method or a sweet design pattern to use the unique abstract DAO, and change the URL invoke way to a clearer and more changeable way.
My code is like:
public static String apiDeviceEdit(Device device, HttpSession session,
Long operate) {
String userId = (String) session
.getAttribute(CommonAttributes.API_USERID_SESSION);
String token = (String) session
.getAttribute(CommonAttributes.API_TOKEN_SESSION);
Assert.notNull(userId);
Assert.notNull(token);
String param = "user_id=" + userId + "&device_sn=" + device.getfSn()
+ "&operate=" + operate + "&token=" + token;
String string = sendPost(setting.getApiDeviceEdit(), param,
"DeviceEdit");
return string;
}
the different Dao like that:
#Component
#Scope("singleton")
public class ConductorDao extends BaseHBDao <Conductor, Long> {
#Autowired
public ConductorDao (#Qualifier("sessionFactory") SessionFactory session) {
super ();
this.setSessionFactory (session);
}
}
public interface ConductorDao extends BaseDao<Conductor, Long> {
final String SELECT_BY_SN = "from Conductor c where c.sn =:sn";
Conductor findBySn(String sn) throws Exception;
Page<Conductor> findByUserId(Long getfId, Pageable pageable);
}
#Component
#Scope("singleton")
public class ConductorDaoImpl extends BaseDaoImpl<Conductor, Long> implements ConductorDao {
#Override
public Conductor findBySn(String sn) throws Exception {
List<Conductor> conductors = entityManager.createQuery(SELECT_BY_SN).setParameter("sn", sn).getResultList();
if (conductors == null || conductors.size()== 0) {
return new Conductor();
}
else {
return conductors.get(0);
}
}
#Override
public Page<Conductor> findByUserId(Long getfId, Pageable pageable) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Conductor> criteriaQuery = criteriaBuilder.createQuery(Conductor.class);
Root<Conductor> root = criteriaQuery.from(Conductor.class);
criteriaQuery.select(root);
Predicate restrictions = criteriaBuilder.conjunction();
restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(root.get("userId"), getfId));
criteriaQuery.where(restrictions);
return super.findPage(criteriaQuery, pageable);
}
}
ok add third layer of abstraction, call it business and make all your call to the dao layer from this new project, cut all the call that goes directly to the dao to use your business tier. then you can use your two jars of the dao in that project, and by doing so you can expose it as rest, or web services if you want.
Related
I feel like this should be pretty straightforward, but I'm not sure about the actual code for it. Basically, I have my rest controller taking in 6 arguments, passing that through the Service and then using those arguments to build the object inside of the ServiceImplementation. From there I return a call to my repo using the object I just made. This call should attempt to query the database specific parameters of the object.
This query is the part where I'm not sure how to write using Spring JPA standards. I'd like to just use the variables I set my object with, but I'm not sure if I'll have to write out a query or if spring JPA can make it a bit more simple?
Code:
Controller:
#RestController
public class exampleController {
#Autowired
private ExampleService exampleService;
#GetMapping("/rest/example/search")
public exampleObj searchExample (#RequestParam(value = "exLetter") String exLetter,
#RequestParam(value = "exLang") String exLang, #RequestParam(value = "exType")int exType,
#RequestParam(value = "exMethod") String exMethod, #RequestParam(value = "exCd") String exCd,
#RequestParam(value = "exOrg") String exOrg) {
return exampleService.getExampleLetter(exLetter, exLang, exType, exMethod, exCd, exOrg);
}
}
ExampleSerivce:
public interface ExampleService {
public ExampleLetter getExampleLetter(String exLetter, String exLang, int exType, String exMethod, String exCd, String exOrg);
}
ExampleServiceImplementation:
#Service
public class ExampleServiceImpl implements ExampleService {
#Autowired
private ExampleRepository exampleRepo;
#Override
public ExampleLetter getExampleLetter(String exLetter, String exLang, int exType, String exMethod, String exCd, String exOrg) {
ExampleLetter examp = new ExampleLetter();
examp.setExCd(exCd);
examp.getKey().setExampleNumber(exLetter);
examp.getKey().setLanguageType(exLang);
examp.getKey().setMethod(exMethod);
examp.getKey().setMarketOrg(exOrg);
examp.getKey().setType(exType);
return exampleRepo.findExampleLetter(examp);
}
}
Repo:
#Repository
public interface ExampleRepository extends CrudRepository<ExampleLetter, ExampleLetterKey> {
}
If I understand it correctly, you are trying to make a dinamic query, based on filtering values that may or may not be there. If that's the case, you can use the Specification class to create the query dinamically:
First, in your Repository class, extend JpaSpecificationExecutor<ExampleLetter>:
#Repository
public interface ExampleRepository extends CrudRepository<ExampleLetter, ExampleLetterKey>, JpaSpecificationExecutor<ExampleLetter> {
}
Now, you will need a method (I'd sugest you put it in an specific class, for organization sake) to generate the query itself:
public class GenerateQueryForExampleLetter {
ExampleLetter exampleLetter;
public Specification<ExampleLetter> generateQuery() {
return new Specification<ExampleLetter>() {
private static final long serialVersionUID = 1L;
#Override
public Predicate toPredicate(Root<ExampleLetter> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Predicate pred = null;
List<Predicate> predicates = new ArrayList<Predicate>();
if (this.exampleLetter.getExCd()!= null && !this.exampleLetter.getExCd().isEmpty()) {
predicates.add(builder.equal(root.<String>get("exCd"), this.exampleLetter.getExCd()));
}
...................
if (this.exampleLetter.getTheFieldYouNeed()!= null && !getTheFieldYouNeed.isEmpty()) {
predicates.add(builder.equal(root.<TheTypeOfTheField>get("theFieldYouNeed"), this.exampleLetter.getTheFieldYouNeed()));
}
if (!predicates.isEmpty()) {
pred = builder.and(predicates.toArray(new Predicate[] {}));
}
return pred;
}
};
}
public void setExampleLetter (ExampleLetter el) {
this.exampleLetter = el;
}
}
Finally, in your service class:
#Override
public ExampleLetter getExampleLetter(String exLetter, String exLang, int exType, String exMethod, String exCd, String exOrg) {
ExampleLetter examp = new ExampleLetter();
examp.setExCd(exCd);
examp.getKey().setExampleNumber(exLetter);
examp.getKey().setLanguageType(exLang);
examp.getKey().setMethod(exMethod);
examp.getKey().setMarketOrg(exOrg);
examp.getKey().setType(exType);
GenerateQueryForExampleLetter queryGenerator = new GenerateQueryForExampleLetter ();
queryGenerator.setExampleLetter(examp);
return exampleRepo.findAll(queryGenerator.generateQuery());
}
Note that the JpaSpecificationExecutor interface adds a few utility methods for you to use which, besides filtering, supports sorting and pagination.
For more details, check here, here, or this answer.
As we all know, there is a big problem with a partial update of the entity. Since the automatic conversion from json strings to the entity, all fields that have not been transferred will be marked null. And as a result, the fields that we did not want to reset will be reset.
I will show the classical scheme:
#RestController
#RequestMapping(EmployeeController.PATH)
public class EmployeeController {
public final static String PATH = "/employees";
#Autowired
private Service service;
#PatchMapping("/{id}")
public Employee update(#RequestBody Employee employee, #PathVariable Long id) {
return service.update(id, employee);
}
}
#Service
public class Service {
#Autowired
private EmployeeRepository repository;
#Override
public Employee update(Long id, Employee entity) {
Optional<T> optionalEntityFromDB = repository.findById(id);
return optionalEntityFromDB
.map(e -> saveAndReturnSavedEntity(entity, e))
.orElseThrow(RuntimeException::new);
}
private T saveAndReturnSavedEntity(Employee entity, Employee entityFromDB) {
entity.setId(entityFromDB.getId());
return repository.save(entity);
}
}
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
and as I have already said that in the current implementation we will not be able to perform a partial update in any way. That is, it is impossible to send an update of only one field in a json line; all fields will be updated, and in null (excepted passed).
The solution to this problem is that you need to perform the conversion from string json to the entity in manual. That is, do not use all the magic from Spring Boot (which is very sad).
I will also give an example of how this can be implemented using merge at the json level:
#RestController
#RequestMapping(EmployeeRawJsonController.PATH)
public class EmployeeRawJsonController {
public final static String PATH = "/raw-json-employees";
#Autowired
private EmployeeRawJsonService service;
#PatchMapping("/{id}")
public Employee update(#RequestBody String json, #PathVariable Long id) {
return service.update(id, json);
}
}
#Service
public class EmployeeRawJsonService {
#Autowired
private EmployeeRepository employeeRepository;
public Employee update(Long id, String json) {
Optional<Employee> optionalEmployee = employeeRepository.findById(id);
return optionalEmployee
.map(e -> getUpdatedFromJson(e, json))
.orElseThrow(RuntimeException::new);
}
private Employee getUpdatedFromJson(Employee employee, String json) {
Long id = employee.getId();
updateFromJson(employee, json);
employee.setId(id);
return employeeRepository.save(employee);
}
private void updateFromJson(Employee employee, String json) {
try {
new ObjectMapper().readerForUpdating(employee).readValue(json);
} catch (IOException e) {
throw new RuntimeException("Cannot update from json", e);
}
}
}
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
With this solution, we eliminate the problem associated with the partial update.
But here another problem arises, that we are losing the automatic addition of validation of beans.
That is, in the first case, validation is enough to add one annotation #Valid:
#PatchMapping("/{id}")
public Employee update(#RequestBody #Valid Employee employee, #PathVariable Long id) {
return service.update(id, employee);
}
But we can't do the same when we perform manual deserialization.
My question is, is there any way to enable automatic validation for the second case?
Or maybe there are other solutions that allow you to use Spring Boot magic for Bean Validation.
What you need is not the normal validation , which can achieved through manual validator call.Let’s now go the manual route and set things up programmatically:
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> violations = validator.validate(object);
for (ConstraintViolation<User> violation : violations) {
log.error(violation.getMessage());
}
To validate a bean, we must first have a Validator object, which is constructed using a ValidatorFactory.
Normal validations on Spring Controllers specified with #Valid annotations are triggered automatically during the DataBinding phase when a request is served.All validators registered with the DataBinder will be executed at that stage. We can't do that for your case, so you can manually trigger the validation like above.
I'm creating a Java application using Elastic Search.
Here is the link for my project.
https://github.com/chanakaDe/ensembl-elastic-rest
In this project, I have implemented a rest controller to take data as JSON.
This is the controller class. Now it only has 2 methods. But I need to add some method like this.
#RequestMapping(value = "/find-by/{id}/{param1}/{param2}/{param3}", method = RequestMethod.GET)
public Iterable<Track> findAllWithParams(#PathVariable int id, #PathVariable String param1, #PathVariable String param2, #PathVariable String param3) {
return trackService.someMethodWithParams(id, param1, param2, param3);
}
What I need to do is take some values from user and send them into Elastic server and make a search. I just refered some of these links and got some idea.
https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-search.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-query.html
TrackService.java and TrackServiceImpl.java are implemented by TrackRepository.java and it's extended by ElasticsearchRepository default class. https://github.com/chanakaDe/ensembl-elastic-rest/blob/master/src/main/java/com/chanaka/book/repository/TrackRepository.java
I need to take values via REST URL and create an object like following and pass that to Elastic Server. How can I implement that with my current project configuration ?
{
"query": {
"constant_score" : {
"filter" : {
"terms" : { "user" : ["kimchy", "elasticsearch"]}
}
}
}
}
This is my TrackService.java interface.
public interface TrackService {
Track save(Track track);
Track findOne(int id);
Iterable<Track> findAll();
}
And also this is my TrackServiceImpl.java class implemented by TrackService.java.
public class TrackServiceImpl implements TrackService {
private TrackRepository trackRepository;
#Autowired
public void setTrackRepository(TrackRepository trackRepository) {this.trackRepository = trackRepository;}
#Override
public Track save(Track track) {
return trackRepository.save(track);
}
#Override
public Track findOne(int id) {
return trackRepository.findOne(id + "");
}
#Override
public Iterable<Track> findAll() {
return trackRepository.findAll();
}
}
Do I need to implement a custom method for that ? Or is there any default methods like findAll() and findOne() ?
Simply pass an object and get the value ?
I think, there's no such existing method and you need to create your own by using QueryBuilders.wrapperQuery(query.toString()) and ElasticsearchTemplate. Just to note, wrapperQuery supports only query not filter. But you can achieve filter context query with constant_score.
I'm trying to add custom methods to my Spring Data repository PersonRepository as described in 1.3 Custom implementations for Spring Data repositories and exposing these method through REST. The initial code is from Accessing JPA Data with REST sample, here is the code for added/modified classes:
interface PersonRepositoryCustom {
List<Person> findByFistName(String name);
}
class PersonRepositoryImpl implements PersonRepositoryCustom, InitializingBean {
#Override
public void afterPropertiesSet() throws Exception {
// initialization here
}
#Override
public List<Person> findByFistName(String name) {
// find the list of persons with the given firstname
}
}
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(#Param("name") String name);
}
When I run the application and visit http://localhost:8080/portfolio/search/, I get the following response body:
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
Why findByFirstName is not exposed even if it is available in the PersonRepository interface?
Also, is there a way to dynamically/programmatically add respositories to be exposed via REST?
After two days, I have solved in this way.
Custom Repository Interface:
public interface PersonRepositoryCustom {
Page<Person> customFind(String param1, String param2, Pageable pageable);
}
Custom Repository Implementation
public class PersonRepositoryImpl implements PersonRepositoryCustom{
#Override
public Page<Person> customFind(String param1, String param2, Pageable pageable) {
// custom query by mongo template, entity manager...
}
}
Spring Data Repository:
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String>, PersonRepositoryCustom {
Page<Person> findByName(#Param("name") String name, Pageable pageable);
}
Bean Resource representation
public class PersonResource extends org.springframework.hateoas.Resource<Person>{
public PersonResource(Person content, Iterable<Link> links) {
super(content, links);
}
}
Resource Assembler
#Component
public class PersonResourceAssembler extends ResourceAssemblerSupport<Person, PersonResource> {
#Autowired
RepositoryEntityLinks repositoryEntityLinks;
public PersonResourceAssembler() {
super(PersonCustomSearchController.class, PersonResource.class);
}
#Override
public PersonResource toResource(Person person) {
Link personLink = repositoryEntityLinks.linkToSingleResource(Person.class, person.getId());
Link selfLink = new Link(personLink.getHref(), Link.REL_SELF);
return new PersonResource(person, Arrays.asList(selfLink, personLink));
}
}
Custom Spring MVC Controller
#BasePathAwareController
#RequestMapping("person/search")
public class PersonCustomSearchController implements ResourceProcessor<RepositorySearchesResource> {
#Autowired
PersonRepository personRepository;
#Autowired
PersonResourceAssembler personResourceAssembler;
#Autowired
private PagedResourcesAssembler<Person> pagedResourcesAssembler;
#RequestMapping(value="customFind", method=RequestMethod.GET)
public ResponseEntity<PagedResources> customFind(#RequestParam String param1, #RequestParam String param2, #PageableDefault Pageable pageable) {
Page personPage = personRepository.customFind(param1, param2, pageable);
PagedResources adminPagedResources = pagedResourcesAssembler.toResource(personPage, personResourceAssembler);
if (personPage.getContent()==null || personPage.getContent().isEmpty()){
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(Person.class);
List<EmbeddedWrapper> embedded = Collections.singletonList(wrapper);
adminPagedResources = new PagedResources(embedded, adminPagedResources.getMetadata(), adminPagedResources.getLinks());
}
return new ResponseEntity<PagedResources>(adminPagedResources, HttpStatus.OK);
}
#Override
public RepositorySearchesResource process(RepositorySearchesResource repositorySearchesResource) {
final String search = repositorySearchesResource.getId().getHref();
final Link customLink = new Link(search + "/customFind{?param1,param2,page,size,sort}").withRel("customFind");
repositorySearchesResource.add(customLink);
return repositorySearchesResource;
}
}
The reason these methods are not exposed is that you're basically free to implement whatever you want in custom repository methods and thus it's impossible to reason about the correct HTTP method to support for that particular resource.
In your case it might be fine to use a plain GET, in other cases it might have to be a POST as the execution of the method has side effects.
The current solution for this is to craft a custom controller to invoke the repository method.
For GET methods I have used the following approach:
create a dummy #Query method in the Repository (LogRepository.java)
create a custom interface with the same method declared (LogRepositoryCustom.java)
create an implementation of the custom interface (LogRepositoryImpl.java)
Using this approach I don't have to manage projections and resource assembling.
#RepositoryRestResource(collectionResourceRel = "log", path = "log")
public interface LogRepository extends PagingAndSortingRepository<Log, Long>,
LogRepositoryCustom {
//NOTE: This query is just a dummy query
#Query("select l from Log l where l.id=-1")
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
public interface LogRepositoryCustom {
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
In the implementation you are free to use the repository methods or going directly to the persistence layer:
public class LogRepositoryImpl implements LogRepositoryCustom{
#Autowired
EntityManager entityManager;
#Autowired
LogRepository logRepository;
#Override
public Page<Log> findAllFilter(
#Param("options") String options,
#Param( "eid") Long[] entityIds,
#Param( "class" ) String cls,
Pageable pageable) {
//Transform kendoui json options to java object
DataSourceRequest dataSourceRequest=null;
try {
dataSourceRequest = new ObjectMapper().readValue(options, DataSourceRequest.class);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
Session s = entityManager.unwrap(Session.class);
Junction junction = null;
if (entityIds != null || cls != null) {
junction = Restrictions.conjunction();
if (entityIds != null && entityIds.length > 0) {
junction.add(Restrictions.in("entityId", entityIds));
}
if (cls != null) {
junction.add(Restrictions.eq("cls", cls));
}
}
return dataSourceRequest.toDataSourceResult(s, Log.class, junction);
}
The answer is that you haven't followed instructions. Your PersonRepository has to extend both PagingAndSortingRepository<Person, Long> AND PersonRepositoryCustomin order to achieve what you're after. See https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations
Another option we used as well is to implement a custom repository factory for your specific storage type.
You can extend from RepositoryFactoryBeanSupport, build your own PersistentEntityInformation and take care of CRUD ops in a default repo impl for your custom data storage type. See JpaRepositoryFactoryBean for example. You maybe need to implement about 10 classes in total but then it gets reusable.
Try using
class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
...
}
The implementing class name should be PersonRepositoryCustomImpl instead of PersonRepositoryImpl.
I have been looking for a way to somehow reduce the amount of code that is duplicated with subtle variance in my Spring MVC controllers, but searching through the SO questions so far has only yielded some questions without any satisfactory answers.
One example of duplication that I want to remove is this, where the user creation page and the role creation page share similarities:
#RequestMapping(value = "user/create", method = RequestMethod.GET)
public String create(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Displaying user creation page.");
return "user/create";
}
#RequestMapping(value = "role/create", method = RequestMethod.GET)
public String create(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Displaying role creation page.");
return "role/create";
}
A slightly more involved variant of duplication that I would like to remove is the one for posting the create form:
#RequestMapping(value = "user/create", method = RequestMethod.POST)
public String save(#ModelAttribute("user") User user, BindingResult errors) {
LOG.debug("Entering save ({})", user);
validator.validate(user, errors);
validator.validatePassword(user, errors);
validator.validateUsernameAvailable(user, errors);
String encodedPassword = encoder.encode(user.getPassword());
user.setPassword(encodedPassword);
if (errors.hasErrors()) {
return create(user, errors);
} else {
service.save(user);
}
return "redirect:/user/index/1";
}
#RequestMapping(value = "role/create", method = RequestMethod.POST)
public String save(#ModelAttribute("role") Role role, BindingResult errors) {
LOG.debug("Entering save({})", role);
validator.validate(role, errors);
if (errors.hasErrors()) {
return create(role, errors);
} else {
service.save(role);
}
return "redirect:/index";
}
This example includes a validate then save if correct and a redirect to the error page if things don't go as planned.
How to remove this duplication?
Spring uses your handler method parameter types to create class instances from the request parameters or body. As such, there is no way to create a handler (#RequestMapping) method that could take an Object and check if it is either a Role or a User. (Technically you could have both parameters and just check which one isn't null, but that is terrible design).
Consequently, you need a handler method for each. This makes sense since, even through the logic is similar, it is still specific to the exact type of model object you are trying to create. You perform different validation, call a different service method, and return a different view name.
I say your code is fine.
Thought I would provide the solution that I settled on in the hope that it might help someone. My gf suggested that I use the name of the entity as a path variable for the controller, and this has proved to provide a very nice solution for the problem at hand.
The two methods now look like this:
#RequestMapping(value = "{entityName}/create", method = RequestMethod.GET)
public String create(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Displaying create page for entity named: [{}]", entityName);
return handlerFactory.getHandler(entityName).getCreateView();
}
#RequestMapping(value = "{entityName}/create", method = RequestMethod.POST)
public String save(#PathVariable("entityName") String entityName, #ModelAttribute("entity") BaseEntity entity, BindingResult errors) {
LOG.debug("Saving entity of type {}", entityName);
CrudHandler handler = handlerFactory.getHandler(entityName);
handler.getCreateValidator().validate(entity, errors);
if (errors.hasErrors()) {
return create(entityName, entity, errors);
}
handler.preSave(entity);
handler.getService().save(entity);
return "redirect:" + DASHBOARD_URL;
}
The CrudHandler interface has implementations for each entity, and provides the controller with the entity specific classes that it needs, such as service and validator. A sample CrudHandler implementation looks like this for me:
#Component
public class RoleCrudHandler implements CrudHandler {
private static final String ENTITY_NAME = "role";
public static final String CREATE_VIEW = "role/create";
public static final String EDIT_VIEW = "role/edit";
#Resource
private RoleService roleService;
#Resource
private RoleValidator validator;
#Resource
private CrudHandlerFactory handlerFactory;
#PostConstruct
public void init() {
handlerFactory.register(ENTITY_NAME, this);
}
#Override
public GenericService getService() {
return roleService;
}
#Override
public Validator getCreateValidator() {
return validator;
}
#Override
public Validator getUpdateValidator() {
return validator;
}
#Override
public BaseEntity createEntity() {
return new Role();
}
#Override
public void preSave(BaseEntity entity) {
}
#Override
public String getCreateView() {
return CREATE_VIEW;
}
#Override
public String getUpdateView() {
return EDIT_VIEW;
}
}
If someone sees some ways to improve this, feel free to share. Hope this will be of use for someone.