I have an #Entity Person class, and want to expose that via a webservice. There should be a method just exposing all details, and and endpoint only exposing a view excerpt.
Can I use Spring #Projection for that purpose without having to manually extract the fields I want to expose? I'd prefer just returning a List<Person> but render only certain details for certain endpoints.
#RestController
public class BookingInfoServlet {
#Autowired
private PersonRepository dao;
#GetMapping("/persons")
public List<Person> persons() {
return dao.findAll();
}
//TODO how can I assign the Projection here?
#GetMapping("/personsView")
public List<Person> persons() {
return dao.findAll();
}
//only expose certain properties
#Projection(types = Person.class)
public interface PersonView {
String getLastname();
}
}
#Entity
public class Person {
#id
long id;
String firstname, lastname, age, etc;
}
interface PersonRepository extends CrudRepository<Person, Long> {
}
Note that #Projection only works with spring data rest. I believe you could try this:
#Projection(name = "personView", types = Person.class)
public interface PersonView {
String getLastname();
}
And on your repo, you need something like this:
#RepositoryRestResource(excerptProjection = PersonView.class)
interface PersonRepository extends CrudRepository<Person, Long> {
}
Related
My MongoDB Schema look like this. So I want to delete any one product using uname(i.e username) and prodname(i.e product name). Can we use #Query annotation to do this or any suggestions?
{
"Id":"string",
"uname":"string",
"products":[
{
"prodname":"string",
"quantity":"int",
"price":"double"
}],
"tot_amt":"double",
}
This is one of my model Cart.java
public class Cart {
#Id
public String Id;
#Indexed(unique=true)
public String uname;
public List<Product>products;
public double tot_amt;
}
This is another model class Product.java
public class Product {
public String prodname;
public int quantity;
public double price;
}
This is the repository interface CartRepository.java
#Repository
public interface CartRepository extends MongoRepository<Cart,String>{
#Query("{uname:?0}")
Optional<Cart> findByName(String name);
}
This is the Service class
public class CartService {
#Autowired
public CartRepository cartRepo;
public MongoTemplate mt;
public void saveUser(Cart cart) {
List<Double>amt= new ArrayList<>();
List<Product>products=cart.getProducts();
products.forEach(p -> {
double price=p.getPrice();
int quantity=p.getQuantity();
amt.add(price*quantity);
});
double tot_amount = 0;
for (Double i : amt)
tot_amount += i;
cart.setTot_amt(tot_amount);
cartRepo.save(cart);
}
public List<Cart> getdata()
{
return cartRepo.findAll();
}
public Optional<Cart> getDetailsByName(String name) {
Optional<Cart> savedCartData=Optional.of(cartRepo.findByName(name).orElseThrow(()->new RuntimeException(String.format("Not found %s",name))));
return savedCartData;
}
If your repository interface looks like this:
public interface YourRepository extends MongoRepository<T, ID> {}
The "YourRepository" will have access to all these methods:
MongoRepository docs
Using #Query you are going to have to make custom queries, and indeed you can use it to perform the deletion you desire.
There is a cool article on Baeldung that can guide you to how you prefer to do it.
A guide to Queries in Spring Data MongoDB
I have created two entites (RegularEmployee and ContactEntity) that extends the Employee entity.
#Entity
#Table(name="employees")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorValue(value="employee")
public class Employee {
#Id
#GeneratedValue
private Long id;
private String name;
...
Im using SINGLE_TABLE inheritance for this implementations, and created a generic JpaRepository for manipulating data:
#Repository
public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
}
I've created also the Service class that autowire three instance of these generic repositories, and specific methods for each class.
#Service
public class EmployeeService {
#Autowired
private EmployeeRepository<Employee> employeeRepo;
#Autowired
private EmployeeRepository<RegularEmployee> regularRepo;
#Autowired
private EmployeeRepository<ContractEmployee> contractRepo;
public List<Employee> getAllEmployee() {
return employeeRepo.findAll();
}
public List<RegularEmployee> getAllRegularEmployee(){
return regularRepo.findAll();
}
public List<ContractEmployee> getAllContractEmployee() {
return contractRepo.findAll();
}
...
My problem is, that when I try to find all regular employees or contract employees, I always get all type of employees (employees, regular employees and contract employees all together).
I do not know why it behaves like this, even though the method's signature says it returns the appropriate type.
One option is to use #Query in EmployeeRepository:
public interface EmployeeRepository<T extends Employee> extends JpaRepository<T, Long> {
#Query("from RegularEmployee")
List<RegularEmployee> findAllRegularEmployees();
}
A second option is to create an additional repository for each subclass of Employee. For RegularEmployee would be:
public interface RegularEmployeeRepository extends EmployeeRepository<RegularEmployee>{}
This is how to use both options in EmployeeService:
#Service
public class EmployeeService {
#Autowired EmployeeRepository<Employee> employeeRepo;
#Autowired EmployeeRepository<RegularEmployee> regularRepoT;
#Autowired RegularEmployeeRepository regularRepo;
#PostConstruct
public void init(){
employeeRepo.save(new ContractEmployee("Mark"));
employeeRepo.save(new RegularEmployee("Luke"));
employeeRepo.findAll().forEach(System.out::println); // prints Mark and Luke
regularRepo.findAll().forEach(System.out::println); // prints only Luke
regularRepoT.findAllRegularEmployees().forEach(System.out::println); // prints only Luke
}
//...
}
Also you can omit #Repository on top of EmployeeRepository. Spring already knows that is a Repository because it extends JpaRepository.
Side note: if you don't need EmployeeRepository to be created by Spring add #NoRepositoryBean on top of its class.
I've been able to replicate what you've encountered using your generic EmployeeRepository. As an alternative I created two separate repositories: ContractualEmployeeRepository and RegularEmployeeRepository.
public interface ContractualEmployeeRepository extends JpaRepository<ContractualEmployee, String> {
}
public interface RegularEmployeeRepository extends JpaRepository<RegularEmployee, String> {
}
Then, I created an integration test.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {Main.class})
#TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
#TestPropertySource(locations="classpath:application-test.properties")
#DatabaseSetup("classpath:SingleTableDataSet.xml")
public class IntegrationTest {
#Autowired
private RegularEmployeeRepository regularEmployeeRepository;
#Autowired
private ContractualEmployeeRepository contractualEmployeeRepository;
#Test
public void test() {
Assert.assertEquals(6, regularEmployeeRepository.findAll().size());
Assert.assertEquals(4, contractualEmployeeRepository.findAll().size());
}
}
and it works.
As for the usage and limitations of Generics in Spring Data JPA repositories: https://stackoverflow.com/a/19443031/14180014 He had done a great job explaining it.
By following the official tutorial for projections in spring data mongodb https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#projections will get an
java.lang.IllegalArgumentException: Couldn't find PersistentEntity for
type class com.sun.proxy.$Proxy109!
for the NamesOnly Projection:
interface NamesOnly {
String getFirstname();
String getLastname();
}
#RepositoryRestResource
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(#Param("lastName") String lastname);
}
Can one get this example to work?
You need to define a #RestController class and call the findByLastname repository method from the controller, like:
#RestController
#RequestMapping("/api")
public class PersonController {
#Autowired
private PersonRepository personRepository;
#GetMapping(path = "/persons/findByLastname")
public Collection<NamesOnly> findByLastname(#Param("lastName") final String lastName) {
Collection<NamesOnly> result = personRepository.findByLastname(lastName);
return result;
}
}
I would like to override the default CrudRepository save method that is also exported to Rest api:
#RepositoryRestResource(path = "users")
public interface UserRepository extends JpaRepository<User, Long> {
#Override
#RestResource(exported=false)
User save(User user);
}
In my ApiController I have set up a requestmapping like this:
#RequestMapping(value = "/", produces = "application/json", method = RequestMethod.POST)
#ResponseBody
public ResponseEntity<Resource<User>> registerUser(
#RequestParam("name") String name,
#RequestParam("alias") String alias,
#RequestParam("email") String email,
#RequestParam("password") String password,
#RequestParam("dateOfBirth") String dateOfBirth,
#RequestParam("imageIdentifier") String imageIdentifier) {
User user = new User();
//try {
// userReposiotry.save(user);
//} catch (Exception e) {
//}
Resource<User> resource = toResource(user);
return new ResponseEntity<Resource<User>>(resource, HttpStatus.OK);
}
The problem is when I try to POST to localhost:8080/api/users it returns a "Method Not allowed" which is good because it was set "exported=false"
But how can I implement my own POST for localhost:8080/api/users ?
Thanks
Another way to do it is to create a custom repository implementation like so:
#RepositoryRestResource(path = "users")
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
#Override
#RestResource(exported=false)
User save(User user);
}
public interface UserRepositoryCustom {
<S extends User> S save(T entity);
}
public UserRepositoryImpl implements UserRepositoryCustom {
<S extends User> S save(T entity) {
// implementation code...
}
}
If you look at the CrudRepository you will find a method <S extends T> S save(S entity);, that's where I got the save(..) from, just changed the extends T to extends User.
The other thing that I would pay attention to is the naming of the classes/interfaces, try to be consistent. The way I named them should work for you, the UserRepositoryImpl must have that name in order for this to work.
Doing this you won't have to set exported=false and you can just use the save() method as you would do normal.
Found a solution:
#BasePathAwareController
#RequestMapping("/users")
public class RestApiController implements ResourceProcessor<Resource<User>>{
#Autowired
private EntityLinks entityLinks;
#RequestMapping(method=RequestMethod.POST)
#ResponseBody
public ResponseEntity<Resource<User>> saveUser(#Param("name") String name) {
// Testing
System.out.println(name);
Resource<User> resource = new Resource<>(new User());
return new ResponseEntity<>(resource , HttpStatus.OK);
}
#Override
public Resource<User> process(Resource<User> resource) {
LinkBuilder lb = entityLinks.linkFor(User.class);
resource.add(new Link(lb.toString()));
return resource;
}
}
The CrudRepository save is still set as exported=false as in my question.
I am not sure of using inheritance / interface implementation in particular situation.
In my simple Spring MVC application I have #Entity class TennisPlayer, which is inherited from abstract class Player (TennisPlayer adds some attributes).
Also I have class TennisPlayerForm, which is inherited from abstract class PlayerForm (TennisPlayerForm adds some attributes again).
User fills the form about tennis player in .jsp page and TennisPlayerForm object is used to represent filled values and then on the basis of this object is created TennisPlayer object and saved into database.
Creation of TennisPlayer object is responsibility of class TennisPlayerDbService. This class is implementation of interface PlayerService.
I have following #Controller, which handles requests:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayerForm") #Valid TennisPlayerForm tennisPlayerForm,
BindingResult result, RedirectAttributes redirectAttributes) {
playerService.createPlayer(tennisPlayerForm);
return "redirect:/allplayers";
}
}
Part of my source code looks like this:
public interface PlayerService {
public void createPlayer(PlayerForm playerForm);
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(PlayerForm playerForm) {
TennisPlayerForm tennisPlayerForm = null;
if (playerForm instanceof TennisPlayerForm) {
tennisPlayerForm = (TennisPlayerForm) playerForm;
}
else {
throw new IllegalArgumentException("Must be of type TennisPlayerForm.");
}
TennisPlayer player = new TennisPlayer();
player.setName(tennisPlayerForm.getName());
player.setSurname(tennisPlayerForm.getSurname());
player.setAge(tennisPlayerForm.getAge());
player.setRacket(tennisPlayerForm.getRacket());
player.setRanking(tennisPlayerForm.getRanking());
player.setSponsor(tennisPlayerForm.getSponsor());
player.setCoach(tennisPlayerForm.getCoach());
player.setClub(tennisPlayerForm.getClub());
dao.saveAndFlush(player);
}
}
Is it justified to use inheritance and interface implementations like this in this situation, when concrete implementation of PlayerService (TennisPlayerDbService) expects instance of particular class, although these potential classes have common parent?
Finally I solved my problem according to your comments and answers.
I deleted PlayerForm abstract class, TennisPlayerForm and mixed javax.validation and javax.persistence annotations in #Entity classes Player and Tennis Player.
Previously mentioned code now looks like this:
#Controller
public class NewPlayerController {
#Resource(name="tennisPlayerService")
private PlayerService<TennisPlayer> playerService;
//omitted RequestMethod.GET handler method
#RequestMapping(value = "/newplayer", method = RequestMethod.POST)
public String newplayer(Locale locale, #ModelAttribute("tennisPlayer") #Valid TennisPlayer tennisPlayer,
BindingResult result, RedirectAttributes redirectAttributes) {
if(result.hasErrors()) {
return "newplayer";
}
playerService.createPlayer(tennisPlayer);
MessageUtil.flash(locale, redirectAttributes, "success", "signup.success");
return "redirect:/allplayers";
}
}
public interface PlayerService<T extends Player> {
public void createPlayer(T player);
public List<T> getAllPlayers();
}
#Service(value="tennisPlayerService")
public class TennisPlayerDbService implements PlayerService<TennisPlayer> {
private TennisPlayerDAO dao;
#Autowired
public void setDao(TennisPlayerDAO dao) {
this.dao = dao;
}
#Override
public void createPlayer(TennisPlayer player) {
dao.saveAndFlush(player);
}
#Override
public List<TennisPlayer> getAllPlayers() {
return dao.findAll();
}
}
Normally your service does not need to know you are working with a form. Your form is purely created to be the model in the model-view-controller architecture of your webpage. (your jsp being the view and your controller being the c-part)
Are you also planning on using other types of players than a TennisPlayer? If not it all seems like premature optimisation and you should keep it as simple as possible.