I'm getting json response from my controller even though I added the xml annotations to my model. I get the list of users in json with no problems. Can I use #Entity and #XmlRootElement in the same class?
User.java
#Entity
#Table(name = "usr")
#XmlRootElement
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", nullable = false, unique = true, length = 11)
private int id;
#Column(nullable = false)
private String username;
#Column(nullable = false)
private String password;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
#Column(nullable = false)
private String email;
#OneToMany(mappedBy = "user")
private List<Post> post;
#OneToMany(mappedBy = "user")
private List<Friend> friends;
public List<Friend> getFriends() {
return friends;
}
public void setFriends(List<Friend> friends) {
this.friends = friends;
}
public List<Post> getPost() {
return post;
}
public void setPost(List<Post> post) {
this.post = post;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#XmlElement
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#XmlElement
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
#XmlElement
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
#XmlElement
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
getAllUsers
#Override
public List<User> getAllUsers() {
List<User> users = new ArrayList<User>();
Session session = HibernateUtil.getSessionAnnotationFactory().openSession();
Transaction trns = null;
try {
trns = session.beginTransaction();
users = session.createQuery("select firstName, lastName, email, username as name from User").list();
trns.commit();
} catch (RuntimeException e) {
if (trns != null) {
trns.rollback();
}
e.printStackTrace();
} finally {
session.flush();
session.close();
}
return users;
}
and in the controller
#RequestMapping(value="/api/users", method = RequestMethod.GET)
public List<User> getAllUsers() {
//List<User> users = new ArrayList<User>();
UserDao userdao = new UserDaoImpl();
List<User> users = userdao.getAllUsers();
return users;
}
What am I missing here?
There is a nice blog entry from Spring about this. From there is this about content negotiation:
Enabling Content Negotiation in Spring MVC
Spring supports a couple of conventions for selecting the format
required: URL suffixes and/or a URL parameter. These work alongside
the use of Accept headers. As a result, the content-type can be
requested in any of three ways. By default they are checked in this
order:
Add a path extension (suffix) in the URL. So, if the incoming URL is
something like http://myserver/myapp/accounts/list.html then HTML is
required. For a spreadsheet the URL should be
http://myserver/myapp/accounts/list.xls. The suffix to media-type
mapping is automatically defined via the JavaBeans Activation
Framework or JAF (so activation.jar must be on the class path).
A URL parameter like this: http://myserver/myapp/accounts/list?format=xls.
The name of the parameter is format by default, but this may be
changed. Using a parameter is disabled by default, but when enabled,
it is checked second.
Finally the Accept HTTP header property is
checked. This is how HTTP is actually defined to work, but, as
previously mentioned, it can be problematic to use.
But if you just want to fix the return type I'd add the annotation #RequestMapping(value="/api/users", produces={"application/xml"}) to your controller method.
First, try to change
#RequestMapping(value="/api/users", method = RequestMethod.GET)
to
#ResponseBody
#RequestMapping(value="/api/users", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
Second, check out if client code ( I assume it is javascript ) is sending correct accept-type, because Jackson - the default serialization engine would derive format of response body from HTTP header.
Third, ensure that you have JAXB present in your classpath.
Related
I am using native query and I get a Tuple with a set of login, email, pass, regDate.
I also created a class with these attributes (This is not an Entity and I don't need it).
Question: How can I get this class using mapstruct (DTO)
#Query("SELECT * FROM users .......", nativeQuery = true)
List<Tuple> getInfo();
#Data
public class UserPro {
String login;
String email;
String pass;
Date regDate;
}
You are almost there but you have some misunderstood concept. If i understood what you are asking...
The "getInfo" query has to obtain every "user" it receives from the query. Everything it receives it's been saved into a "Tuple" List, I suppose Tuple is a model or dto. If you want to save what you receive from that query you have to create a model, not a dto and you may call it "user".
Dtos are classes made to work with data relationated to a Http request, not a query.
Models are classes made to work with data relationated to a database, including database queries.
Inside that "user" model you have to instantiate every column you get from the database (be sure to name the #Column annotation variable the same each column is named on the database and give them the correct data type as in the Database).
Keep in mind that every user has an unique identifier, it doesn't have to be called ID, but you have to know who that unique identifier is.
After instantiating every variable you have to instantiate the getters and setters and a constructor instantiating every variable.
Example:
#Entity
#Table(name = "TABLE_NAME")
public class User implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PUT_HERE_YOUR_UNIQUE_INDENTIFIER_HERE")
private "UNIQUE_INDENTIFIER_DATA_TYPE" id;
#Column(name = "LOGIN_COLUMN_NAME")
private String login;
#Column(name = "EMAIL_COLUMN_NAME")
private String email;
#Column(name = "PASSWORD_COLUMN_NAME")
private String pass;
#Column(name = "REGDATE_COLUMN_NAME", nullable = false)
private Date regDate;
//default constructor
public User() {}
//user constructor
public User()
{
this.id = id;
this.login = login;
this.email = email;
this.pass = pass;
this.regDate = regDate;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getLogin()
{
return login;
}
public void setLogin(String login)
{
this.login = login;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
public String getPass()
{
return pass;
}
public void setPass(String pass)
{
this.pass = pass;
}
public Date getRegDate()
{
return regDate;
}
public void setRegDate(Date regDate)
{
this.regDate = regDate;
}
}
Doing a project with parcel service. I created OrderItem API and Dispatcher API. Now, I want to connect then by relations. The idea is: dispatcher can have many orderItems. OrderItem can only have one dispatcher. If you delete dispatcher, his order items also has to go out.
I have already created a little bit, but I'm so messed up here and can't finish this thing logically. Would someone give me some ideas on how I should attack this problem.
Do I need to put relations both sides or only to one of them?
When do I need to create constructors with arguments? Because in entity class you have to have no arg constructors...?
OrderItem class:
#Entity
public class OrderItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotBlank(message = "Order weight is required")
private String weight;
#NotBlank(message = "Order dimensions are required")
private String dimensions;
#NotBlank(message = "Order origin is required")
private String origin;
#NotBlank(message = "Order destination is required")
private String destination;
#NotNull(message = "Order comment cannot be null")
private String comment;
#ManyToOne
private Dispatcher dispatcher;
public OrderItem() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
public String getDimensions() {
return dimensions;
}
public void setDimensions(String dimensions) {
this.dimensions = dimensions;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Dispatcher getDispatcher() {
return dispatcher;
}
public void setDispatcher(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
}
OrderController class:
#RestController
#RequestMapping("/order")
public class OrderController {
#Autowired
OrderService service;
#Autowired
private MapValidationErrorService mapValidationErrorService;
#GetMapping("/{dispatcherId}/orders")
public List<OrderItem> getAllOrderItems(#PathVariable int dispatcherId) {
return service.getAllOrderItems(dispatcherId);
}
#PostMapping("/{dispatcherId}/orders")
public ResponseEntity<?> saveOrder(#Valid #RequestBody OrderItem orderItem, #PathVariable int dispatcherId, BindingResult result) {
ResponseEntity<?> errorMap = mapValidationErrorService.MapValidationService(result);
if (errorMap != null) {
return errorMap;
}
orderItem.setDispatcher(new Dispatcher(dispatcherId, "", "", ""));
service.insertOrUpdate(orderItem);
return new ResponseEntity<String>("Order was created successfully", HttpStatus.CREATED);
}
#PutMapping("/update")
public ResponseEntity<?> updateOrder(#Valid #RequestBody OrderItem orderItem, BindingResult result) {
ResponseEntity<?> errorMap = mapValidationErrorService.MapValidationService(result);
if (errorMap != null) {
return errorMap;
}
service.insertOrUpdate(orderItem);
return new ResponseEntity<String>("Order was updated successfully", HttpStatus.OK);
}
#GetMapping("/all")
public Iterable<OrderItem> getAllOrders() {
return service.findAllOrders();
}
#DeleteMapping("/{orderId}")
public ResponseEntity<String> deleteOrder(#PathVariable int orderId) {
if (service.findById(orderId) == null) {
throw new CustomErrorException("Order doesn't exist, check order id");
}
service.deleteOrder(orderId);
return new ResponseEntity<String>("Order with ID " + orderId + " was deleted", HttpStatus.OK);
}
#GetMapping("/{orderId}")
public ResponseEntity<OrderItem> getOrderById(#PathVariable int orderId) {
OrderItem item = service.findById(orderId);
if (service.findById(orderId) == null) {
throw new CustomErrorException("Order id not found - " + orderId);
}
return new ResponseEntity<OrderItem>(item, HttpStatus.OK);
}
}
Dispatcher class:
#Entity
public class Dispatcher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#NotBlank(message = "Dispatcher first name is required")
private String firstName;
#NotBlank(message = "Dispatcher last name is required")
private String lastName;
#NotBlank(message = "Dispatcher email name is required")
private String email;
#NotBlank(message = "Dispatcher email is required")
private String password;
#NotBlank(message = "Dispatcher phone number is required")
private String phoneNumber;
public Dispatcher() {
}
public Dispatcher(int id, String firstName, String lastName, String email) {
super();
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
DispatcherController class:
#RestController
#RequestMapping("/dispatcher")
public class DispatcherController {
#Autowired
DispatcherService service;
#Autowired
private MapValidationErrorService mapValidationErrorService;
#PostMapping("/save")
public ResponseEntity<?> saveDispatcher(#Valid #RequestBody Dispatcher dispatcher, BindingResult result) {
ResponseEntity<?> errorMap = mapValidationErrorService.MapValidationService(result);
if (errorMap != null) {
return errorMap;
}
service.insertOrUpdate(dispatcher);
return new ResponseEntity<String>("Dispatcher was created successfully", HttpStatus.CREATED);
}
#GetMapping("/all")
public Iterable<Dispatcher> getAllDispatchers() {
return service.findAllDispatchers();
}
#GetMapping("/{dispatcherId}")
public ResponseEntity<?> getDispatcherById(#PathVariable int dispatcherId) {
Dispatcher dispatcher = service.findById(dispatcherId);
if (service.findById(dispatcherId) == null) {
throw new CustomErrorException("Dispatcher id not found - " + dispatcherId);
}
return new ResponseEntity<Dispatcher>(dispatcher, HttpStatus.OK);
}
#DeleteMapping("/{dispatcherId}")
public ResponseEntity<?> deleteDispatcher(#PathVariable int dispatcherId) {
if (service.findById(dispatcherId) == null) {
throw new CustomErrorException("Dispatcher doesn't exist, check dispatcher id");
}
service.deleteDispatcher(dispatcherId);
return new ResponseEntity<String>("Order with ID " + dispatcherId + " was deleted", HttpStatus.OK);
}
#PutMapping("/update")
public ResponseEntity<?> updateDispatcher(#Valid #RequestBody Dispatcher dispatcher, BindingResult result) {
ResponseEntity<?> errorMap = mapValidationErrorService.MapValidationService(result);
if (errorMap != null) {
return errorMap;
}
service.insertOrUpdate(dispatcher);
return new ResponseEntity<String>("Dispatcher was updated successfully", HttpStatus.OK);
}
}
I think you have defined the relationship incorrectly. And yes you need to have no-args constructor. This helps hibernate to map the values from database to java objects when retrieving data from the database
Assuming you are going for a uni-directional mapping,
#Entity
public class OrderItem {
#ManyToOne( cascade = CascadeType.ALL )
#JoinColumn(name = <foriegn_key_column in orderItem table i.e. id>)
private Dispatcher dispatcher;
}
#Entity
public class Dispatcher {
private List<OrderItem > orders;
}
The main difference is that bidirectional relationship gives you access in both directions. so that you can access the other side without any queries. It works for cascade actions too.
The bidirectional #OneToMany generates better DML because the #ManyToOne owns the relationship.
Unidirectional #ManyToOneor bidirectional #OneToMany are more efficient than unidirectional #OneToMany.
Before JPA 2.0 this unidirection #OneToMany used a join table to manage the association between parent and child rows. So higher cost in read (3 tables join) and write (3 tables insertion).
Since JPA 2.0 for unidirectional #OneToMany you should use it in correlation with #JoinColumn
With the #JoinColumn the #OneToMany association controls the child table FK.. and so no need for extra junction table.
But performance wise there is no better than bidirectional associations.
Pros of unidirectional #OneToMany -> simplicity.
For your second question : NoArg is required only by the persistence framework (Hibernate for e.g). But you can (and should) use your own constructors to create consistent objects.
I'm trying to add a functionality to my webapp with user registration. Webapp is based on spring boot, hibernate and mysql database, frontend is in angular. Generally, the user creation procedure is working correctly, user data is correctly send from frontend to backend via json and saved to the database in shop_user table (with all the user data, such as name, surname, address etc.), but it DOESN'T have role column.
I also have table 'role', which should be:
id name
1 USER
2 ADMIN
and joined table user_role, which consists of user_id from table shop_user and role id from table role, so it should look like this:
id_user id_role
1 2
2 1
3 1
When user is being created on the website, it is hard-coded to set the role by default to USER. This seems to work quite well as it adds a new row in shop_user, and it adds a row to user_role, but... it also creates a new row in 'role' table.
so in the end 'role' table looks like this:
id name
1 ADMIN
2 USER
3 USER
4 USER
5 USER
99 USER
`
while this is not a blocking bug that stops application from working, it is not 'as it should work' unfortunately... as the table should only consist of two role rows (and possibly additional ones, in the future), but not multiplicated for each user!
here's the flawed code of user:
User
#Entity
#Table(name = "shop_user")
public class User extends AbstractEntity {
#Column
private String firstName;
#Column
private String lastName;
#Column
private String addressLine;
#Column
private String city;
#Column
private String country;
#Column
private String zipCode;
#Column
private String phoneNumber;
#Column
private String email;
#Column
private String password;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = #JoinColumn(name = "id_user", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "id_role", referencedColumnName = "id"),
uniqueConstraints = {#UniqueConstraint(columnNames = {"id_user", "id_role"})})
private List<Role> roles;
public User() {
}
public User(User user) {
setId(user.getId());
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.addressLine = user.getAddressLine();
this.city = user.getCity();
this.country = user.getCountry();
this.zipCode = user.getZipCode();
this.phoneNumber = user.getPhoneNumber();
this.email = user.getEmail();
this.password = user.getPassword();
this.roles= user.getRoles();
}
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getAddressLine() {
return addressLine;
}
public void setAddressLine(String addressLine) {
this.addressLine = addressLine;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Role implementation:
Role
#Entity
#Table(name = "role")
public class Role extends AbstractEntity {
#Column
private String name;
#ManyToMany(mappedBy = "roles", cascade = CascadeType.PERSIST)
private List<User> users;
public Role(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
Abstract entity:
AbstractEntity
#MappedSuperclass
public abstract class AbstractEntity implements Persistable<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public void setId(Long id) {
this.id = id;
}
#Override
public Long getId() {
return id;
}
#Override
public boolean isNew() {
return id == null;
}
}
User service:
UserServiceImpl
#Service
public class UserServiceImpl extends AbstractServiceImpl<User, UserDTO> implements UserService {
private final UserRepository userRepository;
private final UserConverter userConverter;
public UserServiceImpl(UserRepository userRepository, UserConverter
userConverter) {
this.userRepository = userRepository;
this.userConverter = userConverter;
}
#Override
protected JpaRepository<User, Long> getRepository() {
return userRepository;
}
#Override
protected Converter<User, UserDTO> getConverter() {
return userConverter;
}
#Override
#Transactional
public User registerUser(User user) {
List<Role> roles = new LinkedList<>();
roles.add(new Role("USER"));
user.setRoles(roles);
return userRepository.save(user);
}}
I am nearly sure that this comes to the relations mapping in Hibernate and object creation, but can't quite figure it out...
Any help will be appreciated, thank you!
The issue is here:
#Override
#Transactional
public User registerUser(User user) {
List<Role> roles = new LinkedList<>();
roles.add(new Role("USER"));
user.setRoles(roles);
return userRepository.save(user);
}}
Since the relationship User -> Role is cascade persist, the (new) role new Role("USER") is also persisted and you ended up with a new Role for each user instead of reusing the existing one.
The solution is to check the existence of a Role with name = USER. If doesn't exist, insert it. Otherwise add the existent one to the roles collection.
I'm trying to return an Object as JSON.
Using the /user/id endpoint, I want to display a User based on his Id.
When calling this controllerMethod I get the following Exception:
InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.sample.scrumboard.models.User_$$_jvsta02_1["handler"])
My contollerClass looks like this:
#RestController
#RequestMapping(path="/user")
#JsonIgnoreProperties(ignoreUnknown = true)
public class UserRestController {
private UserRepository repository;
#Autowired
public UserRestController(UserRepository repository){
this.repository = repository;
}
#GetMapping(value = "/list")
public List<User> getUsers(){
return repository.findAll();
}
#GetMapping(value = "/{id}")
public #ResponseBody User getUserById(#PathVariable Long id, User user){
user = repository.getOne(id);
return user;
}
}
I checked if al fields have a public getter and tried various options with #JSONIgnoreProperties, but I can't find it.
Displaying all users as a JSONlist does work JSONlist with /user/list. So the problem is only there when trying to display one Object, not a list of Objects.
From the repository it does find the User, but it's unable to serialize that Object and put in on the screen.
The User class itself looks like this:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userId", nullable = false, updatable = false)
private Long id;
#NotNull
#Size(min=2, max=20)
private String firstName;
#NotNull
#Size(min=2, max=30)
private String lastName;
#NotNull
#Size(min=2, max=20)
private String userName;
#NotNull
#Size(min=2, max=30)
private String passWord;
#NotNull
#Email
private String email;
//the mappedBy element must be used to specify the relationship field or property of the entity that is the owner of the relationship
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonIgnore
private List<UserStory> userStoryList;
public User() {
}
public User(String firstName, String lastName, String userName, String passWord, String email) {
this.firstName = firstName;
this.lastName = lastName;
this.userName = userName;
this.passWord = passWord;
this.email = email;
}
#Override
public String toString() {
return "User{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", userName='" + userName + '\'' +
", passWord='" + passWord + '\'' +
", email='" + email + '\'' +
'}';
}
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 getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<UserStory> getUserStoryList() {
return userStoryList;
}
public void setUserStoryList(List<UserStory> userStoryList) {
this.userStoryList = userStoryList;
}
}
How can I display my User returned from /user/id?
A Solution?
As suggested below, I made it work using a Dto and ModelMapper.
I added
#Bean
public ModelMapper modelMapper(){
return new ModelMapper();
}
ControllerMethod
#GetMapping(value = "/{id}")
public UserDTO getUserById(#PathVariable Long id, User user, ModelMapper modelMapper){
user = repository.getOne(id);
return modelMapper.map(user, UserDTO.class);
}
And UserDto
public class UserDTO {
private Long id;
private String firstName;
private String lastName;
private String userName;
private String passWord;
private String email;
private List<UserStory> userStoryList;
//getters and setters
Now I'm able to show a User on the screen.
Still I'm wondering if there is no solution using Jackson and without modelmapper and dto?
Try adding the following line to your application.properties file :
spring.jackson.serialization.fail-on-empty-beans=false
Maybe it's not a good idea to use your entity (User) to expose the data about user via REST? Can you create UserDTO for your user that will implement Serializable and send this DTO via REST? In this case it should be necessary to convert User object that you've retrieved from the db to UserDTO.
Don't use #JsonIgnoreProperties(ignoreUnknown = true) in the controller class.
Use the below annotation in the entity class. That solved that issue.
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
Annotation #JsonIgnoreProperties should not be on UserRestController, what you need to serialize is User, remove annotation is just ok. Jackson will help to do all the work, if you want to ignore some fields on class User, move #JsonIgnoreProperties to User class, and add #JsonProperty on filed need to display on the page.
Serializability of your orm classes implementing the java.io.Serializable interface
You are using #RestController, there is no need of #ResponseBody
Also there is no need to create a DTO class to transform the ORM class for the response json.
OBS: For the Bidirectional reationship you will use #JsonManagedReference, #JsonBackReference
There is a different between spring repository.getOne() and repository.findById(). With getOne() you can get a reference (proxy) e.g. if the object is already read in the same transaction. With findById() you always get the User as expected.
i am apply the
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
spring.jackson.serialization.fail-on-empty-beans=false
in my cause . it was not working fine.
I'm making my first web-app using Spring Boot, Thymeleaf, Hibernate (with in-memory H2 database). I also want to use Spring Security but I try to avoid it for now (want to finish Entities and repositories first although I know I will have to change the way they work) I want a user to register on my site (he is created and added to my db) and then he can CRUD his ToDos (which are also saved to db). I have connected both entitites with bidirectional OneToMany using annotations and #JoinTable/#mappedBy. Here are my entities:
ToDoItem.java
#Entity
#Table (name = "TO_DO_ITEM")
public class ToDoItem extends BaseEntity {
#Column(name = "TITLE", nullable = false)
private String title;
#Column(name = "COMPLETED")
private boolean completed;
// TODO: 29.01.17 Add description property
#Column(name = "DUE_DATE", nullable = false)
private LocalDate dueDate;
// two-directional OneToMany
#ManyToOne
#JoinColumn(name = "USER_ID")
private User user;
// JPA demands empty contructor
ToDoItem() {}
public ToDoItem(String title, LocalDate dueDate) {
this.title = title;
this.dueDate = dueDate;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
}
public LocalDate getDueDate() {
return dueDate;
}
public void setDueDate(LocalDate dueDate) {
this.dueDate = dueDate;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
User.java
#Entity
#Table (name = "USERS")
public class User extends BaseEntity {
#Column(name = "USERNAME")
private String username;
// TODO: 28.01.17 Find a way to store hashed and salted pws in DB
#Column(name = "PASSWORD")
private String password;
#Column(name = "EMAIL")
private String email;
#OneToMany(mappedBy = "user")
private Set<ToDoItem> toDoItems = new HashSet<>();
// JPA demands empty constructor
User() {}
public User(String username, String password, String email) {
this.username = username;
this.password = password;
this.email = email;
}
public Set<ToDoItem> getToDoItems() {
return toDoItems;
}
public void setToDoItems(Set<ToDoItem> toDoItems) {
this.toDoItems = toDoItems;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
I am initialized a set in User.java so I can add created items by logged in user to his own set so they are not just sent to the db, but they are actually connected. Now I just need a method to add newly created item by him to his set (and to the db).
I have two Service beans:
ToDoItemServiceBean.java
#Service
public class ToDoItemServiceBean implements ToDoItemService {
#Autowired
private ToDoItemRepository toDoItemRepository;
#Override
public Iterable<ToDoItem> listAllToDoItems() {
return toDoItemRepository.findAll();
}
#Override
public ToDoItem getToDoItemById(Long id) {
return toDoItemRepository.findOne(id);
}
#Override
public ToDoItem addNewToDo(ToDoItem toDoItem) {
return toDoItemRepository.save(toDoItem);
}
#Override
public void deleteToDo(Long id) {
toDoItemRepository.delete(id);
}
}
UserServiceBean.java
#Service
public class UserServiceBean implements UserService {
#Autowired
private UserRepository userRepository;
#Override
public User saveUser(User user) {
return userRepository.save(user);
}
}
And I have no clue how to add those ToDos to a specific user in db. Should I just add an item to a set in addNewToDo so apart from adding new ToDo to db this method would also somehow connect ToDo with a user? But how do I do it? What's the best approach to fix it?
HERE is the rest of my code (didn't want to paste all of my files into this post).
EDIT:
I rethought my db structure and I think that I should have OneToMany in UserEntity because User can have many ToDos but OneToOne in ToDoItemEntity because a ToDo can have only one user - that's correct, right?
And from that link I made something like this, is it ok?
ToDoItemEntity
// a ToDoItem is only associated with one user
#OneToOne(cascade = CascadeType.ALL, mappedBy = "toDoItems")
private User user;
UserEntity
//user can have many ToDoItems
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private Set<ToDoItem> toDoItems;
Added updateUser to:
UserServiceBean.java
#Override
public void updateUser(ToDoItem toDoItem) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User queriedUser = userRepository.findOne(currentUser.getId());
queriedUser.setToDoItems();
}
you can add ToDoItems to the user and save the user entity. Make use of Cascade operations to cascade the operation
http://howtodoinjava.com/hibernate/hibernate-jpa-cascade-types/
// two-directional OneToMany
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
update : UserServiceBean.java
public void updateUser(User user){
// update the user entity here by adding the todoitems and call saveuser again
}
Update 2 : ToDoItemController
#Override
public ToDoItem addNewToDo(ToDoItem toDoItem) {
return toDoItemRepository.updateItems(toDoItem);
}
ToDoItemServiceBean :
public void updateItems(ToDoItem toDoItem) {
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User queriedUser = userRepository.findOne(currentUser.getId());
toDoItem.setUser(queriedUser);
saveToDoItem(toDoItem)// since it is bidirectional
}