I am learning Play Framework and I need to bind Address to Person from web form.
There are my entities:
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String country;
#OneToMany(mappedBy = "address", fetch = FetchType.EAGER)
private List<Person> persons;
// get/set
}
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Address address;
// get/set
}
I have folllowing controller and dao:
public class HomeController extends Controller {
#Inject
private Configuration configuration;
#Inject
private PersonManager personManager;
#Inject
private AddressManager addressManager;
#Inject
FormFactory formFactory;
#Inject
Formatters formatters;
#Transactional
public Result index() {
List<Person> persons = personManager.findAll();
List<Address> addresses = addressManager.findAll();
return ok(index.render("Hello, world", persons, addresses));
}
#Transactional
public Result addPerson() {
DynamicForm requestData = formFactory.form().bindFromRequest();
System.out.println("form: " + requestData.get("address"));
Person person = Form.form(Person.class).bindFromRequest().get();
personManager.save(person);
return redirect(routes.HomeController.index());
}
#Transactional
public Result addAddress() {
Address address = Form.form(Address.class).bindFromRequest().get();
addressManager.save(address);
return redirect(routes.HomeController.index());
}
// ...
}
package dao;
public class PersonDaoImpl implements PersonDao {
#Inject
private JPAApi jpaApi;
#Override
public void save(Person user) {
jpaApi.em().persist(user);
}
#Override
public void delete(Person user) {
jpaApi.em().remove(user);
}
#Override
public void update(Person user) {
jpaApi.em().merge(user);
}
#Override
public List<Person> findAll() {
return jpaApi.em().createQuery("from Person").getResultList();
}
#Override
public Person findById(int id) {
return (Person) jpaApi.em().find(Person.class, id);
}
#Override
public Person findByName(String name) {
return (Person) jpaApi.em().createQuery("SELECT p FROM Person p WHERE p.name LIKE :name").setParameter("name", name).getSingleResult();
}
}
package dao;
public class AddreessDaoImpl implements AddressDao {
#Inject
private JPAApi jpaApi;
#Override
public void save(Address address) {
jpaApi.em().persist(address);
}
#Override
public void delete(Address address) {
jpaApi.em().remove(address);
}
#Override
public void update(Address address) {
jpaApi.em().merge(address);
}
#Override
public List<Address> findAll() {
return jpaApi.em().createQuery("from Address").getResultList();
}
#Override
public Address findById(int id) {
return (Address) jpaApi.em().find(Address.class, id);
}
#Override
public Address findByCountry(String name) {
return (Address) jpaApi.em().createQuery("SELECT a FROM Address a WHERE a.country LIKE :country").setParameter("country", name).getSingleResult();
}
}
View:
#(message: String, persons: List[Person], addresses: List[Address])
#main("Welcome to Play") {
<form action="#routes.HomeController.addPerson()" method="post">
<input type="text" name="name" />
<select name="address">
#for(address <- addresses) {
<option value="#address.getId">
#address.getCountry
</option>
}
</select>
<button>Add Person</button>
</form>
}
I need to add new person with existing Address. I'm trying to bind address to person using Formatters:
package service;
import dao.AddressManager;
import models.Address;
import play.data.format.Formatters;
import play.i18n.MessagesApi;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import java.text.ParseException;
import java.util.Locale;
#Singleton
public class FormattersProvider implements Provider<Formatters> {
private final MessagesApi messagesApi;
#Inject
public FormattersProvider(MessagesApi messagesApi) {
this.messagesApi = messagesApi;
}
#Inject
private AddressManager addressManager;
#Override
public Formatters get() {
Formatters formatters = new Formatters(messagesApi);
formatters.register(Address.class, new Formatters.SimpleFormatter<Address>(){
#Override
public Address parse(String input, Locale locale) throws ParseException {
Address address = addressManager.findById(Integer.getInteger(input));
return address;
}
#Override
public String print(Address address, Locale locale) {
return address.getCountry();
}
});
return formatters;
}
}
package service;
import com.google.inject.AbstractModule;
import play.data.format.Formatters;
public class FormattersModule extends AbstractModule {
#Override
protected void configure() {
bind(Formatters.class).toProvider(FormattersProvider.class);
}
}
But how can I use Formatters?
If I add address, it is ok.
But if I add new Person using web form address column in db have NULL value, or app crash:
[error] application -
! #720fgnbfl - Internal server error, for (POST) [/person] ->
play.api.http.HttpErrorHandlerExceptions$$anon$1: Execution exception[[CompletionException: java.lang.IllegalStateException: Error(s) binding form: {"address":["Invalid value"]}]]
at play.api.http.HttpErrorHandlerExceptions$.throwableToUsefulException(HttpErrorHandler.scala:280)
at play.api.http.DefaultHttpErrorHandler.onServerError(HttpErrorHandler.scala:206)
at play.api.GlobalSettings$class.onError(GlobalSettings.scala:160)
at play.api.DefaultGlobal$.onError(GlobalSettings.scala:188)
at play.api.http.GlobalSettingsHttpErrorHandler.onServerError(HttpErrorHandler.scala:98)
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:100)
at play.core.server.netty.PlayRequestHandler$$anonfun$2$$anonfun$apply$1.applyOrElse(PlayRequestHandler.scala:99)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:344)
at scala.concurrent.Future$$anonfun$recoverWith$1.apply(Future.scala:343)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
Caused by: java.util.concurrent.CompletionException: java.lang.IllegalStateException: Error(s) binding form: {"address":["Invalid value"]}
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:292)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:308)
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:593)
at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:577)
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:474)
at java.util.concurrent.CompletableFuture.completeExceptionally(CompletableFuture.java:1977)
at scala.concurrent.java8.FuturesConvertersImpl$CF.apply(FutureConvertersImpl.scala:21)
at scala.concurrent.java8.FuturesConvertersImpl$CF.apply(FutureConvertersImpl.scala:18)
at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
at scala.concurrent.BatchingExecutor$Batch$$anonfun$run$1.processBatch$1(BatchingExecutor.scala:63)
Caused by: java.lang.IllegalStateException: Error(s) binding form: {"address":["Invalid value"]}
at play.data.Form.get(Form.java:634)
at controllers.HomeController.addPerson(HomeController.java:106)
at router.Routes$$anonfun$routes$1$$anonfun$applyOrElse$2$$anonfun$apply$2.apply(Routes.scala:293)
at router.Routes$$anonfun$routes$1$$anonfun$applyOrElse$2$$anonfun$apply$2.apply(Routes.scala:293)
at play.core.routing.HandlerInvokerFactory$$anon$4.resultCall(HandlerInvoker.scala:157)
at play.core.routing.HandlerInvokerFactory$$anon$4.resultCall(HandlerInvoker.scala:156)
at play.core.routing.HandlerInvokerFactory$JavaActionInvokerFactory$$anon$14$$anon$3$$anon$1.invocation(HandlerInvoker.scala:136)
at play.core.j.JavaAction$$anon$1.call(JavaAction.scala:73)
at play.http.HttpRequestHandler$1.call(HttpRequestHandler.java:54)
at play.db.jpa.TransactionalAction.lambda$call$4(TransactionalAction.java:28)
Why? How can I bind it?
Related
I have modelled a car park with building and floor models. There is a one to many relationship between building and floor. I have built a rest controllers to retrieve the data. I am attempting to retrive the data via a simple get request to api/v1/parkingbuildings/1/. The issue is that when retrieving a building i do not see a list of floors as per my relation mapping. Any insight into any mistakes i may be making would be appreciated. Below is the json that gets returned;
{"building_id":1,"building_name":"Labadiestad","postcode":"SA78BQ","max_floors":14,"owner_name":"Schaefer, Gutmann and Braun"}
I am expecting to see a collection of floors in the payload and i cannot fathom why, ive written other similar simpler solutions that do the same without issue, ive compared my prior solutions and see little difference that matters in my approach.
Here is my building model
package com.admiral.reslink.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
#Entity(name = "parking_buildings")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class ParkingBuilding {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long building_id;
private String building_name;
private String postcode;
private int max_floors;
private String owner_name;
// ToDo sort the relationships
#OneToMany(mappedBy = "parkingBuilding")
#JsonIgnore
private List<ParkingFloor> parkingFloors;
public ParkingBuilding() {
}
public long getBuilding_id() {
return building_id;
}
public void setBuilding_id(long building_id) {
this.building_id = building_id;
}
public String getBuilding_name() {
return building_name;
}
public void setBuilding_name(String building_name) {
this.building_name = building_name;
}
public String getPostcode() {
return postcode;
}
public void setPostcode(String postcode) {
this.postcode = postcode;
}
public int getMax_floors() {
return max_floors;
}
public void setMax_floors(int max_floors) {
this.max_floors = max_floors;
}
public String getOwner_name() {
return owner_name;
}
public void setOwner_name(String owner_name) {
this.owner_name = owner_name;
}
public List<ParkingFloor> getParkingFloors() {
return parkingFloors;
}
public void setParkingFloors(List<ParkingFloor> parkingFloors) {
this.parkingFloors = parkingFloors;
}
}
And here is my floor model
package com.admiral.reslink.models;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name = "parking_floors")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class ParkingFloor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long floor_id;
private int floor_number;
private int max_height_inches;
private boolean is_covered;
private boolean is_disabled_access;
// ToDo sort the relationships
#ManyToOne
#JoinColumn(name="building_id", nullable=false)
private ParkingBuilding parkingBuilding;
#OneToMany(mappedBy = "parkingFloor")
#JsonIgnore
private List<ParkingSpace> parkingSpace;
public ParkingFloor() {
}
public long getFloor_id() {
return floor_id;
}
public void setFloor_id(long floor_id) {
this.floor_id = floor_id;
}
public int getFloor_number() {
return floor_number;
}
public void setFloor_number(int floor_number) {
this.floor_number = floor_number;
}
public int getMax_height_inches() {
return max_height_inches;
}
public void setMax_height_inches(int max_height_inches) {
this.max_height_inches = max_height_inches;
}
public boolean isIs_covered() {
return is_covered;
}
public void setIs_covered(boolean is_covered) {
this.is_covered = is_covered;
}
public boolean isIs_disabled_access() {
return is_disabled_access;
}
public void setIs_disabled_access(boolean is_disabled_access) {
this.is_disabled_access = is_disabled_access;
}
public ParkingBuilding getParkingBuilding() {
return parkingBuilding;
}
public void setParkingBuilding(ParkingBuilding parkingBuilding) {
this.parkingBuilding = parkingBuilding;
}
public List<ParkingSpace> getParkingSpace() {
return parkingSpace;
}
public void setParkingSpace(List<ParkingSpace> parkingSpace) {
this.parkingSpace = parkingSpace;
}
}
Here is my building controller
package com.admiral.reslink.controllers;
import com.admiral.reslink.models.ParkingBuilding;
import com.admiral.reslink.models.ParkingFloor;
import com.admiral.reslink.repositories.ParkingBuildingRepository;
import com.admiral.reslink.repositories.ParkingFloorRepository;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#RequestMapping("/api/v1/parkingbuildings")
public class ParkingBuildingController {
#Autowired
private ParkingBuildingRepository parkingBuildingRepository;
#GetMapping
public List<ParkingBuilding> list() {return parkingBuildingRepository.findAll();}
#GetMapping
#RequestMapping("{id}")
public ParkingBuilding get(#PathVariable Long id) {return parkingBuildingRepository.getById(id);}
#PostMapping
public ParkingBuilding create(#RequestBody final ParkingBuilding parkingBuilding) {
return parkingBuildingRepository.saveAndFlush(parkingBuilding);
}
#RequestMapping(value="{id}", method = RequestMethod.DELETE)
public void delete(#PathVariable Long id) {
parkingBuildingRepository.deleteById(id);
}
#RequestMapping(value="{id}", method = RequestMethod.PUT)
public ParkingBuilding update(#PathVariable Long id, #RequestBody ParkingBuilding parkingBuilding) {
ParkingBuilding existingParkingBuilding = parkingBuildingRepository.getById(id);
BeanUtils.copyProperties(parkingBuilding, existingParkingBuilding, "building_id");
return parkingBuildingRepository.saveAndFlush(existingParkingBuilding);
}
}
Not sure how you are retrieving the floors. OneToMany is by default lazy and would not load unless asked.
In your repository, try:
#EntityGraph(attributePaths = "parkingFloors")
ParkingBuilding findById(long id);
import io.micronaut.core.annotation.NonNull;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.r2dbc.annotation.R2dbcRepository;
import io.micronaut.data.repository.reactive.ReactiveStreamsCrudRepository;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Post;
import io.micronaut.runtime.Micronaut;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}
#Controller
#RequiredArgsConstructor
class CustomerController {
private final CustomerRepository customerRepository;
#Get
public Flux<Customer> getAll() {
return customerRepository.findAll();
}
#Get("/test")
public Mono<Customer> test() {
return Mono.from(customerRepository.findById(1L)).map(e -> {
System.out.println(e);
return e;
});
// System.out.println(customerRepository);
// return Mono.just("TEST");
}
#Post
public Mono<Customer> create(#Body Customer customer) {
Customer saveCustomer = new Customer(customer.getName(), customer.getSurname());
return Mono.from(customerRepository.save(saveCustomer));
}
}
//#R2dbcRepository(dialect = Dialect.SQL_SERVER)
interface CustomerRepository extends ReactiveStreamsCrudRepository<Customer, Long> {
// #NonNull
// #Override
// Mono<Customer> findById(#NonNull #NotNull Long id);
//
// #NonNull
// #Override
// Mono<Customer> save(#NonNull #NotNull Customer customer);
//
#NonNull
#Override
Flux<Customer> findAll();
}
#MappedEntity("customer")
class Customer {
#Id
#GeneratedValue
private Long id;
private final String name;
private final String surname;
public Customer(String name, String surname) {
this.name = name;
this.surname = surname;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}
**
Trying to call a repositry through endpoint but keep getting error
**
Context does not contain key: io.micronaut.tx.STATUS
at reactor.util.context.Context1.get(Context1.java:67)
at io.micronaut.data.r2dbc.operations.DefaultR2dbcRepositoryOperations.lambda$withTransaction$27(DefaultR2dbcRepositoryOperations.java:441)
at reactor.core.publisher.FluxDeferContextual.subscribe(FluxDeferContextual.java:49)
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:62)
at reactor.core.publisher.FluxDefer.subscribe(FluxDefer.java:54)
at reactor.core.publisher.InternalFluxOperator.subscribe(InternalFluxOperator.java:62)
I've faced the same issue.
Check this:
#Get
public Flowable<Customer> getAll() {
return Flowable.fromPublisher(customerRepository.findAll());
}
and where is Mono use Single.fromPublisher(yourMono)
I encountered the same issue with micronaut 3.
After some tests i found that, in my specific case, this issue was caused by using the following modules:
micronaut-security-jwt
micronaut-reactor
micronaut-jackson-xml
If you remove all of these dependencies, the issue disappear.
I think that this problem is caused by the above micronaut modules, so i opened an issue on the micronaut-r2dbc github page with the hope they solve this issue ASAP.
EDIT:
they fixed this bug releasing the new micronaut version 3.0.1!!!
This is my project directory structure.
All controller and other classes and directories, which are beans, are under the "WebPortalApplication" class, and as Spring Boot doc states, we do not explicitly specify the package to scan for beans, whenever those packages locate unther the "main" class directory, right?
So when I run the "WebPortalApplication" file, it builds, but with such exceptions.
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userRestController': Unsatisfied dependency expressed through field 'userService';
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'roleRepository';
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'roleRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException:
Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.epam.webPortal.model.Role
#RestController
public class UserRestController {
#Autowired
UserService userService;
private static final Logger LOGGER = LoggerFactory.getLogger(UserRestController.class);
//-------------------Retrieve All Users--------------------------------------------------------
#RequestMapping(value = "/user/", method = RequestMethod.GET)
public String listAllUsers() {
String userAsJson = "";
List<User> users = userService.findAllUsers();
try {
userAsJson = JsonConvertor.toJson(users);
} catch (Exception ex) {
LOGGER.error("Something went wrong during converting json format");
}
LOGGER.info("displaying all users in json format");
return userAsJson;
}
package com.epam.webPortal.service.user;
import com.epam.webPortal.model.User;
import com.epam.webPortal.repository.role.RoleRepository;
import com.epam.webPortal.repository.user.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional
import java.util.Date;
import java.util.HashSet;
import java.util.List;
#Service("userService")
#Transactional
public class UserServiceImpl implements UserService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
public void saveUser(User user) {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
user.setRoles(new HashSet<>(roleRepository.findAll()));
user.setDateRegistered(new Date());
userRepository.save(user);
LOGGER.info("user with username {} successfully saved", user.getUsername());
}
#Override
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
#Override
public List<User> findAllUsers() {
return userRepository.findAllUsers();
}
#Override
public User findById(Long Id) {
return userRepository.findById(Id);
}
#Override
public void updateUser(User user) {
final User entity = userRepository.findById(user.getId());
if (entity != null) {
entity.setFirstName(user.getFirstName());
entity.setLastName(user.getLastName());
entity.setEmail(user.getEmail());
entity.setSkypeID(user.getSkypeID());
entity.setDateRegistered(new Date());
entity.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRepository.save(entity);
LOGGER.info("user with id {} successfully updated", user.getId());
}
}
#Override
public void deleteUserById(Long id) {
userRepository.deleteById(id);
LOGGER.info("user with id {} successfully deleted", id);
}
}
package com.epam.webPortal.model;
import javax.persistence.*;
import java.util.Set;
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER)
private Set<User> users;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
It looks like you're using JPA.
All JPA entities must be annotated with #Entity.
Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.epam.webPortal.model.Role
means, Role class is missing #Entity annotation.
Have you enabled your repos?
#SpringBootApplication
#EnableJpaRepositories
public class WebPortalApplication {
public static void main(String[] args) {
SpringApplication.run(WebPortalApplication.class, args);
}
}
I expect below definition on your RoleRepository; sharing this file will help to further analyze on what is missing.
public interface RoleRepository implements CrudRepository<Role, Long> {
...
}
My professor gave a sample Spring MVC ORM project with Hibernate but I can not figure out the sequence of events involved, in particular about the usage of service business object.
This is just a little part of the project, just to make my ideas clearer.
domain:
#Entity
#Table(name = "department")
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long uid;
private String name;
#OneToMany(mappedBy="department",cascade=CascadeType.PERSIST)
private List<Employee> employees = new ArrayList<Employee>();
public Department() {
}
public Department(String name) {
this.name = name;
}
// getters, setters, hashcode() and equals(), toString()...
controller:
#Controller
#RequestMapping("/department")
public class DepartmentController {
#Autowired
#Qualifier("departmentBO")
private DepartmentBO departmentBO;
static final Logger logger = Logger.getLogger(DepartmentController.class);
#RequestMapping(value = "/home", method = RequestMethod.GET)
public String departmentHome(Model model) {
logger.debug("department home() invoked");
List<Department> list = departmentBO.findAllDepartments();
model.addAttribute("list", list);
return "departments";
}
// i'll paste just the first controller ;)
business:
public interface DepartmentBO {
public void delete(long uid);
public List<Department> findAllDepartments();
public Department findByUid(Long uid);
public void save(Department department);
public void update(Department department);
}
business/impl:
#Service
#Transactional
public class DepartmentBoImpl implements DepartmentBO {
#Autowired
private DepartmentDAO departmentDao;
static final Logger logger = Logger.getLogger(DepartmentBoImpl.class);
#Override
public void save(Department department) {
departmentDao.save(department);
}
#Override
public void update(Department department) {
departmentDao.update(department);
}
#Override
public void delete(long uid) {
departmentDao.delete(uid);
}
#Override
public List<Department> findAllDepartments() {
return departmentDao.findAllDepartments();
}
#Override
public Department findByUid(Long uid) throws DataAccessException {
return departmentDao.findByUid(uid);
}
}
dao:
public interface DepartmentDAO {
public void delete(long uid);
public List<Department> findAllDepartments();
public Department findByUid(Long uid);
public void save(Department user);
public void update(Department user);
}
dao/impl:
#Repository
public class DepartmentDAOImplSf implements DepartmentDAO {
#Autowired
private SessionFactory sessionFactory;
#Override
public void delete(long uid) {
Department department = (Department) sessionFactory.getCurrentSession()
.get(Department.class, uid);
sessionFactory.getCurrentSession().delete(department);
}
#Override
public void save(Department department) {
sessionFactory.getCurrentSession().save(department);
}
#Override
public void update(Department department) {
sessionFactory.getCurrentSession().saveOrUpdate(department);
}
#Override
public List<Department> findAllDepartments() {
List<Department> list = (List<Department>) sessionFactory
.getCurrentSession()
.createQuery("FROM Department").list();
return list;
}
#Override
public Department findByUid(Long uid) {
Department department = (Department) sessionFactory
.getCurrentSession().get(Department.class, uid);
return department;
}
}
I know that the order is: domain model -> controller-> service -> dao ->db, but why use a DepartmentBO? and why DepartmentBoImpl autowired DepartmentDao? Who of them act first? Something that i'm not understanding is messing up my conception of how it works and the sequence of the process..
Thanks for your help ;)
EDIT: "
In few words my question is, what is the sequence of this code? user goes on the /home page that redirect on "departments" page. But what happen before this --> "List list = departmentBO.findAllDepartments();" ?;)
When the departmentBO.findAllDepartments() method is called if you look at the code it invokes the sessionFactory. That is an internal factory class in Hibernate that basically builds a transactional connection to the DB in order to run a query. You are defining the query in the createQuery method and then ultimately executing it with the list() method. These two methods are part of the database session that Hibernate has instantiated.
Departments Page -> departmentBO.findAllDepartments() -> sessionFactory -> createQuery -> list()
Or in pseudo code-ish
Departments Page -> execute findAllDepartments method -> fetch / build a database connection -> define the query -> execute the query -> Return the list!
Have a strange problem and can't figure out how to deal with it.
Have simple POJO:
#Entity
#Table(name = "persons")
public class Person {
#Id
#GeneratedValue
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "middle_name")
private String middleName;
#Column(name = "last_name")
private String lastName;
#Column(name = "comment")
private String comment;
#Column(name = "created")
private Date created;
#Column(name = "updated")
private Date updated;
#PrePersist
protected void onCreate() {
created = new Date();
}
#PreUpdate
protected void onUpdate() {
updated = new Date();
}
#Valid
#OrderBy("id")
#OneToMany(mappedBy = "person", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private List<PhoneNumber> phoneNumbers = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Date getCreated() {
return created;
}
public Date getUpdated() {
return updated;
}
public List<PhoneNumber> getPhoneNumbers() {
return phoneNumbers;
}
public void addPhoneNumber(PhoneNumber number) {
number.setPerson(this);
phoneNumbers.add(number);
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
#Entity
#Table(name = "phone_numbers")
public class PhoneNumber {
public PhoneNumber() {}
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
#Id
#GeneratedValue
private Long id;
#Column(name = "phone_number")
private String phoneNumber;
#ManyToOne
#JoinColumn(name = "person_id")
private Person person;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
and rest endpoint:
#ResponseBody
#RequestMapping(method = RequestMethod.GET)
public List<Person> listPersons() {
return personService.findAll();
}
In json response there are all fields except Id, which I need on front end side to edit/delete person. How can I configure spring boot to serialize Id as well?
That's how response looks like now:
[{
"firstName": "Just",
"middleName": "Test",
"lastName": "Name",
"comment": "Just a comment",
"created": 1405774380410,
"updated": null,
"phoneNumbers": [{
"phoneNumber": "74575754757"
}, {
"phoneNumber": "575757547"
}, {
"phoneNumber": "57547547547"
}]
}]
UPD Have bidirectional hibernate mapping, maybe it's somehow related to issue.
I recently had the same problem and it's because that's how spring-boot-starter-data-rest works by default. See my SO question -> While using Spring Data Rest after migrating an app to Spring Boot, I have observed that entity properties with #Id are no longer marshalled to JSON
To customize how it behaves, you can extend RepositoryRestConfigurerAdapter to expose IDs for specific classes.
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
#Configuration
public class RepositoryConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Person.class);
}
}
In case you need to expose the identifiers for all entities:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private EntityManager entityManager;
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(
entityManager.getMetamodel().getEntities().stream()
.map(Type::getJavaType)
.toArray(Class[]::new));
}
}
Note that in versions of Spring Boot prior to 2.1.0.RELEASE you must extend the (now deprecated) org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter instead of implement RepositoryRestConfigurer directly.
If you only want to expose the identifiers of entities that extends or
implements specific super class or interface:
...
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(
entityManager.getMetamodel().getEntities().stream()
.map(Type::getJavaType)
.filter(Identifiable.class::isAssignableFrom)
.toArray(Class[]::new));
}
If you only want to expose the identifiers of entities with a specific annotation:
...
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(
entityManager.getMetamodel().getEntities().stream()
.map(Type::getJavaType)
.filter(c -> c.isAnnotationPresent(ExposeId.class))
.toArray(Class[]::new));
}
Sample annotation:
import java.lang.annotation.*;
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ExposeId {}
Answer from #eric-peladan didn't work out of the box, but was pretty close, maybe that worked for previous versions of Spring Boot. Now this is how it is supposed to be configured instead, correct me if I'm wrong:
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
#Configuration
public class RepositoryConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(User.class);
config.exposeIdsFor(Comment.class);
}
}
The class RepositoryRestConfigurerAdapter has been deprecated since 3.1, implement RepositoryRestConfigurer directly.
#Configuration
public class RepositoryConfiguration implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(YouClass.class);
RepositoryRestConfigurer.super.configureRepositoryRestConfiguration(config);
}
}
Font: https://docs.spring.io/spring-data/rest/docs/current-SNAPSHOT/api/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.html
With Spring Boot you have to extends SpringBootRepositoryRestMvcConfiguration
if you use RepositoryRestMvcConfiguration the configuration define in application.properties may not worked
#Configuration
public class MyConfiguration extends SpringBootRepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Project.class);
}
}
But for a temporary need
You can use projection to include id in the serialization like :
#Projection(name = "allparam", types = { Person.class })
public interface ProjectionPerson {
Integer getIdPerson();
String getFirstName();
String getLastName();
}
Just add #JsonProperty annotation to the Id and it works.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty
private long id;
another approach is to implement RepositoryRestConfigurerAdapter in configuration. (This approach will be usefull when you have to do marshalling in many places)
#Component
public class EntityExposingIdConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
try {
Field exposeIdsFor = RepositoryRestConfiguration.class.getDeclaredField("exposeIdsFor");
exposeIdsFor.setAccessible(true);
ReflectionUtils.setField(exposeIdsFor, config, new ListAlwaysContains());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
class ListAlwaysContains extends ArrayList {
#Override
public boolean contains(Object o) {
return true;
}
}
}
Hm, ok seems like I found the solution. Removing spring-boot-starter-data-rest from pom file and adding #JsonManagedReference to phoneNumbers and #JsonBackReference to person gives desired output. Json in response isn't pretty printed any more but now it has Id. Don't know what magic spring boot performs under hood with this dependency but I don't like it :)
Easy way: rename your variable private Long id; to private Long Id;
Works for me. You can read more about it here
Implement the RepositoryRestConfigurer and use #Configuration annotation on the class.
Here's the snippet
#Configuration
public class BasicConfig implements RepositoryRestConfigurer{
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
// TODO Auto-generated method stub
config.exposeIdsFor(Person.class);
}
}
You can also use the static configuration method to easily enable exposing ids in a few lines.
From the Spring Data Rest RepsositoryRestConfigurer docs:
static RepositoryRestConfigurer withConfig(Consumer<RepositoryRestConfiguration> consumer)
Convenience method to easily create simple RepositoryRestConfigurer instances that solely want to tweak the RepositoryRestConfiguration.
Parameters:
consumer - must not be null.
Since:
3.1
So this works for me in an existing #Configuration-annotated class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
#Configuration
public class ApplicationConfiguration {
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return RepositoryRestConfigurer.withConfig(repositoryRestConfiguration ->
repositoryRestConfiguration.exposeIdsFor(Person.class)
);
}
}