Creating custom functions in Spring Boot services - spring

I am writing a simple Spring Boot Application.I'm creating a repository,then its service and then its implementation
The code works fine if I use inbuilt functions of the JPA repository.
However it throws error if I try to make a function in the service interface.
If I make that function in the repository it doesn't throw that error
Here is the code:
Repository:
#Transactional
public interface Local_Repository extends JpaRepository<LocalModel,Long> {
}
Service:
public interface Local_Service {
public List<LocalModel> findAll();
public LocalModel findById(Long id);
public LocalModel findBymo(String mo);//this is the function I added
}
Implementation:
#Service
public class Local_Impl implements Local_Service {
#Autowired
private Local_Repository repository;
#Override
public List<LocalModel> findAll() {
List<LocalModel> cities = (List<LocalModel>) repository.findAll();
return cities;
}
#Override
public LocalModel findById(Long id) {
LocalModel city = repository.findOne(id);
return city;
}
#Override //this throws error
public LocalModel findBymo(String mo) {
LocalModel city=repository.findBymo(mo);
return null;
}
}

In the service you are calling a function that doesn't exist in the repository layer. There is no method called findBymo in the repository. If LocalModel has a field called mo, you can just add a function in the repository interface like this
LocalModel findByMo(String mo);
and it will be implemented automatically.
If LocalModel doesn't have such field you should implement the query yourself in the repository like this
#Query("select ... query here")
LocalModel findByMo(String mo);

Related

Spring JPA : REQUIRES_NEW propagation not working

I have the following scenario where I have one controller containing two functions (saveAudit and saveProduct). Each one persists an object,I would like to separate transactions between those functions.
throwed exception on saveProduct function should not rollback transaction on saveAudit function :
My repositories/ DAO :
public interface AuditRepository extends JpaRepository<Audit, String> {
}
public interface ProductRepository extends JpaRepository<Product, String> {
}
My controller:
#RestController
#Transactional
public class ProductController {
private final ProductreRepository productRepository;
private final Auditrepository auditRepository;
#Transactional(propagation=Propagation.REQUIRES_NEW)
void saveAudit()
{
auditRepository.saveAudit(Audit.builder().action("action1").build());
}
#PostMapping(ApiPaths.PRODUCTS)
#ResponseStatus(HttpStatus.CREATED)
public ProductDTO addNewProduct() {
ProductDTO res = productRepository.saveProduct(Product.builder().label("product1").build());
saveAudit();
int h=1/0; // => throw exception to rollback product creation
return res;
}
}
Logs:
Participating in existing transaction
its same class proxy will not work.
move below method to #Service class and inject in your controller or annotate #Transactional(propagation=Propagation.REQUIRES_NEW) in auditRepository.saveAudit
#Transactional(propagation=Propagation.REQUIRES_NEW)
public void saveAudit()
{
auditRepository.saveAudit(Audit.builder().action("action1").build());
}

How to ignore some parameter in JPA

I pass arguments to get data from database by JPA.
There are two arguments.
The first argument is ServiceType ; to switch database from Aspect (AOP)
The second argument is used to make query.
However there is org.springframework.data.jpa.repository.query.ParameterBinder.bind error
in conclusion, how to ignore the first(ServiceType) argument to make JPA query.
The code is like that.
#Repository
public interface BlogRepository extends JpaRepository<Blog, Integer> {
List<Blog> findByName(ServiceType serviceType, String name, Pageable pageable);
}
#Aspect
#Order(1)
#Component
public class MDBDecisionAspect {
private static ThreadLocal<ServiceType> localService = new ThreadLocal<>();
#Pointcut("execution(public * com.test.jpa.mdb..*.*(com.test.enums.ServiceType, ..))")
public void repositoryAspectTarget() {
}
#Around("repositoryAspectTarget()")
public Object initDaoService(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args[0] != null && ServiceType.class.equals(args[0].getClass())) {
setServiceType(((ServiceType) args[0]));
}
return joinPoint.proceed();
}
#AfterReturning("repositoryAspectTarget()")
public void afterInitDaoServiceReturningTargetMethod() {
setServiceType(null);
}
public static ServiceType getServiceType() {
return localService.get();
}
public static void setServiceType(ServiceType serviceType) {
localService.set(serviceType);
}
}
create a service class and call repository in it. So you can use serviceType inside service class.
#Repository
public interface BlogRepository extends JpaRepository<Blog, Integer> {
List<Blog> findByName(String name, Pageable pageable);
}
class BlogService{
public List<Blog> getListOfBlog(ServiceType st, String name){
blogRepository. findByName(name);
}
}
change aspect pointcut to check service class.

How to override super interface #PreAuthorize expression

I have a generic service with annotations, this is extended by every service interface. How can I override this annotation from the service interface?
I put the #PreAuthorize annotation on the service interface but it did not work, however, when I put this on the service implementation it did.
My generic service interface looks like this:
public interface GenericService<T, ID> {
#PreAuthorize(/*Generic condition*/)
T get(ID id);
}
My entity's service interface (where I want this to work)
public interface EntityService extends GenericService<Entity, Integer> {
#PreAuthorize(/*Specific condition*/)
Entity get(Integer id);
}
My entity's service implementation (where it does work)
#Service
public class EntityServiceImpl extends AbstractServiceImpl<Entity, Integer> implements EntityService {
#Override
#PreAuthorize(/*Specific condition*/)
public Entity get(Integer id) {
return super.get(id);
}
}
Slice of my abstract service implementation class:
public abstract class AbstractServiceImpl<T extends AbstractEntity<ID>, ID> implements GenericService<T, ID> {
#Override
public T get(ID id) {
return repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException(id));
}
}
I don't want to put this on the implementation because this is not specific to it. It must be the same condition across every posible implementation of the service.

Using Mockito to mock out Spring Boot repository delete call throws java.util.NoSuchElementException on get()

I am new to Spring Boot and Mockito and having a problem mocking out a repository call in my service test.
I have a "delete" service method call as follows that I am trying to test with Mockito by mocking out the repository calls:
public interface IEntityTypeService {
public EntityType getById(long id);
public EntityType getByName(String name);
public List<EntityType> getAll();
public void update(EntityType entityType);
public void delete(long id);
public boolean add(EntityType entityType);
}
#Service
public class EntityTypeServiceImpl implements IEntityTypeService {
#Autowired
private EntityTypeRepository entityTypeRepository;
#Override
public void delete(long id) {
entityTypeRepository.delete(getById(id));
}
#Override
public EntityType getById(long id) {
return entityTypeRepository.findById(id).get();
}
....implementation of other methods from the interface
}
My repository looks as follows:
#RepositoryRestResource
public interface EntityTypeRepository extends LookupObjectRepository<EntityType> {
}
I have not implemented any of the methods in the repository as I am letting Spring Boot wire it up for me.
My test is as follows:
#RunWith(SpringRunner.class)
public class EntityTypeServiceTest {
#TestConfiguration
static class EntityTypeServiceImplTestContextConfiguration {
#Bean
public IEntityTypeService entityTypeService() {
return new EntityTypeServiceImpl();
}
}
#Autowired
private IEntityTypeService entityTypeService;
#MockBean
private EntityTypeRepository entityTypeRepository;
#Test
public void whenDelete_thenObjectShouldBeDeleted() {
final EntityType entity = new EntityType(1L, "new OET");
Mockito.when(entityTypeRepository.findById(1L).get()).thenReturn(entity).thenReturn(null);
// when
entityTypeService.delete(entity.getID());
// then
Mockito.verify(entityTypeRepository, times(1)).delete(entity);
assertThat(entityTypeRepository.findById(1L).get()).isNull();
}
}
When I run the test, I get an error saying "java.util.NoSuchElementException: No value present"
java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at xyz.unittests.service.EntityTypeServiceTest.whenDelete_thenObjectShouldBeDeleted(OriginatingEntityTypeServiceTest.java:41)
It references the line in the test saying Mockito.when(originatingEntityTypeRepository.findById(1L).get()).thenReturn(entity).thenReturn(null);
The reason I think I have to mock that call out is because the delete method in the Service calls the getById() method in the same service, which in turn calls entityTypeRepository.findById(id).get()
It is that, that I am assuming I have to mock out on the delete. But clearly I am wrong. Any assistance would be appreciated.
Many thanks
#Test
public void whenDelete_thenObjectShouldBeDeleted() {
final EntityType entity = new EntityType(1L, "new OET");
Optional<EntityType> optionalEntityType = Optional.of(entity);
Mockito.when(entityTypeRepository.findById(1L)).thenReturn(optionalEntityType);
// when
entityTypeService.delete(entity.getID());
// then
Mockito.verify(entityTypeRepository, times(1)).delete(entity);
//I dont think you need to assert to confirm actual delete as you are testing mock registry. to assert somethink like below you need to return null by mocking the same call again and return the null but thats of no use
//assertThat(entityTypeRepository.findById(1L).get()).isNull();
}
Updated your test. Basically we first need to mock the result of findById. refer my comment above asserting the actual delete.

requestfactory complain about find method

I have a spring (3.1) application with a service and dao layer.
I try to use requestfactory (gwt 2.4) withi this spring layer.
Here some of my class
My domain class
public class Account {
Long id;
String username;
// get, set
}
The bridge between spring and gwt
public class SpringServiceLocator implements ServiceLocator {
#Override
public Object getInstance(Class<?> clazz) {
HttpServletRequest request = RequestFactoryServlet.getThreadLocalRequest();
ServletContext servletContext = request.getSession().getServletContext();
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
return context.getBean(clazz);
}
}
My account proxy
#ProxyFor(value=Account.class, locator = AccountLocator.class)
public interface AccountProxy extends EntityProxy{
public Long getId();
public String getUsername();
public void setUsername(String userName);
public void setId(Long id);
}
RequestContext class
#Service(locator = SpringServiceLocator.class, value =AccountService.class)
public interface AccountRequest extends RequestContext {
Request<List<AccountProxy>> loadAllAccounts();
}
My requestFactory class
public interface AccountRequestFactory extends RequestFactory {
AccountRequest accountRequest();
}
My spring service
public interface AccountService {
public List<Account> loadAllAccounts();
}
#Service
public class AccountServiceImpl implements AccountService{
#Autowired
private AccountDAO accountDAO;
}
Account locator to avoid to put method in the entity
public class AccountLocator extends Locator<Account, Long> {
#Autowired
private AccountDAO accountDAO;
#Override
public Account create(Class<? extends Account> clazz) {
return new Account();
}
}
applicationContext.xml
<context:annotation-config />
<context:component-scan base-package="com.calibra" />
<bean id="accountService" class="org.calibra.server.service.AccountServiceImpl"/>
<bean id="accountDAO" class="org.calibra.server.dao.AccountDAOImpl"/>
The demo work but i get this error:
com.google.web.bindery.requestfactory.server.UnexpectedException: Could not find static method with a single parameter of a key type
Also on my AccountProxy i get this complain (a warning)
The domain type org.calibra.domain.Account has no Account findAccount(java.lang.Long) method. Attempting to send a AccountProxy to the server will result in a server error.
I don't want to add a find methond in my domain class.
I tried to put this method in my spring service, but i get the same warning.
Edit with the Locator that work fine
Just strange i need to put bean in the applicationContext, context:annotation and context:component-scan seem useless
Any idea?
The domain type org.calibra.domain.Account has no Account findAccount(java.lang.Long) method.
If you don't provide a find method of some kind, RequestFactory has no way of reconstituting objects when they get to the server - it can only create brand new ones, which prevents it from merging with existing data. Take this away, and you might as well have RPC again.
If you don't want static methods, provide a Locator instance which is able to find objects. From https://developers.google.com/web-toolkit/doc/latest/DevGuideRequestFactory#locators:
What if you don't want to implement persistence code in an entity itself? To implement the required entity locator methods, create an entity locator class that extends Locator:
public class EmployeeLocator extends Locator<Employee, Long> {
#Override
public Employee create(Class<? extends Employee> clazz)
{
return new Employee();
}
...
}
Then associate it with the entity in the #ProxyFor annotation:
#ProxyFor(value = Employee.class, locator = EmployeeLocator.class)
public interface EmployeeProxy extends EntityProxy {
...
}
You'll need to implement all of the methods, not just create - and the main one you are interested in is find(Class, Long). It may be possible to use one single Locator type for all proxies - as of 2.4.0 and 2.5.0-rc1 it is safe to fail to implement getDomainType(), and all of the other methods that need to know the exact type are provided with it as an argument.
Here is an example of what this can look like with JPA and Guice, but I think the idea is clear enough that it can be implemented with Spring and whatever persistence mechanism you are using. Here, all entities are expected to implement HasVersionAndId, allowing the locator to generalize on how to invoke getVersion and getId - you might have your own base class for all persisted entities.
(from https://github.com/niloc132/tvguide-sample-parent/blob/master/tvguide-client/src/main/java/com/acme/gwt/server/InjectingLocator.java)
public class InjectingLocator<T extends HasVersionAndId> extends Locator<T, Long> {
#Inject
Provider<EntityManager> data;
#Inject
Injector injector;
#Override
public T create(Class<? extends T> clazz) {
return injector.getInstance(clazz);
}
#Override
public T find(Class<? extends T> clazz, Long id) {
return data.get().find(clazz, id);
}
#Override
public Class<T> getDomainType() {
throw new UnsupportedOperationException();//unused
}
#Override
public Long getId(T domainObject) {
return domainObject.getId();
}
#Override
public Class<Long> getIdType() {
return Long.class;
}
#Override
public Object getVersion(T domainObject) {
return domainObject.getVersion();
}
}

Resources