A Contract has several AbsenceType, and an AbsentType can be in several different Contracts. So I made a manyToMany relation in both classes.
Entity
#Table(name = "contract")
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
#EntityListeners(AuditingEntityListener.class)
public class Contract {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "employee")
private int employee;
#ManyToMany(mappedBy = "contracts")
private List<AbsenceType> absence_types;
// ... getteur setteur contructor
#Entity
#Table(name = "absence_type")
#EntityListeners(AuditingEntityListener.class)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class AbsenceType {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
// hexa : #FFFFFF
#Column(name = "color")
private String color;
#Column(name = "is_paid")
private boolean is_paid;
#ManyToMany
#Column(name = "contracts")
private List<Contract> contracts;
// ... getteur setteur contructor
I want to be able to create empty Absence types and then when I create a contract, give in my json, the previously created absenceType and not create a new AbsenceType.
In the idea I do this:
{
"id": 1 # Exemple, in real in don't had id on json
"name": "Congé sans solde",
"color": "ff4040",
"is_paid": false,
"contracts": []
}
and after ->
{
"name": "Contract4",
"employee": 3,
"absence_types" : [
{
"id":1
}
]
}
But the response when i get all contracts is:
{
"id": 621,
"name": "Contract4",
"employee": 3,
"absenceTypes": []
}
But i want :
{
"id": 621,
"name": "Contract4",
"employee": 3,
"absenceTypes": [
{
"id": 1,
"name": "Congé sans solde",
"color": "ff4040",
"contracts" : [ dudo, i don't think about infinite recursion problem for the moment haha]
}
}
For all of my DAO i have a generic class, how look like this
public void save(T obj) {
Session session = openSession();
Transaction transaction = session.beginTransaction();
session.saveOrUpdate(obj);
transaction.commit();
session.close();
}
and on my Contract controller
#PostMapping("")
public ResponseEntity<String> create(#RequestBody Contract contract) {
// Error 422 if the input role variable is null
if (contract == null)
new ResponseEntity<String>("Can't create null absenceType", HttpStatus.UNPROCESSABLE_ENTITY);
contractDAO.save(contract);
return new ResponseEntity<>("absenceType saved !", HttpStatus.OK);
}
Related
How can I form JSON like that:
{
"error": false,
"errorCode": 0,
"message": [{
"id": "93",
"venueName": "Sushi Kuni",
"venueAddress": "10211 S De Anza Blvd Cupertino CA",
"showDate": "1531022400",
"showTime": "",
"description": ""
}, {
"id": "38",
"venueName": "Two Keys Tavern",
"venueAddress": "333 S Limestone Lexington KY",
"showDate": "1531368000",
"showTime": "8 pm - 1 am",
"description": ""
}]
}
I tried creating models with one-to-one, many-to-one relationships.
#Entity
#Table(name = "tutorials")
public class Tutorial {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "tutorial_generator")
private int id;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#Column(name = "published")
private boolean published;
#Entity
#Table(name = "comments")
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "comment_generator")
private int id;
#Lob
private String content;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "tutorial_id", nullable = false)
#JsonIgnore
private Tutorial tutorial;
Controller:
public ResponseEntity<List<Comment>> getAllCommentsByTutorialId(#PathVariable(value = "tutorialId") int tutorialId) {
if (!tutorialRepository.existsById(tutorialId)) {
throw new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId);
}
List<Comment> comments = commentRepository.findByTutorialId(tutorialId);
return new ResponseEntity<>(comments, HttpStatus.OK);
}
But I got
[
{
"id": 1,
"content": "asdaasfaf"
},
{
"id": 3,
"content": "ssaduy7tjyjt"
},
{
"id": 4,
"content": null
}
]
I read about creating nodes, but didn't understand, how integrate it with spring data.
What i need to nest table Comments in table Tutorials?
Looks to me as if you are pretty close to the desired outcome.
First of all you should add a member to your Tutorial entity:
#OneToMany(mappedBy = "tutorial_id")
private List<Comment> comments;
And the final missing part is making your controller return a tutorial instance:
public ResponseEntity<Tutorial> getTutorialWithComments(#PathVariable(value = "tutorialId") int tutorialId) {
Tutorial tutorial = tutorialRepository.findById(tutorialId)
.orElseThrows(() -> new ResourceNotFoundException("Not found Tutorial with id = " + tutorialId));
return new ResponseEntity<>(tutorial, HttpStatus.OK);
}
I wrote this without any IDE assistance, so please excuse any mistakes. The idea should be clear though, you need to return a tutorial that contains a list of comments.
I have two entity User and Group and the relationship between both entities are many-to-many. When I call view-group/groupName, I am getting the list of users of group as expected. But when I call view-user/userEmail, I am not getting the list of groups with user details of which user is part of.
Group.java
#Entity
#Table(name = "group_")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
private String groupName;
#ManyToMany
#JoinTable(name = "group_user",
joinColumns = { #JoinColumn(name = "group_id") },
inverseJoinColumns = {#JoinColumn(name = "user_id") })
public Set<User> usersOfgroup = new HashSet<>();
public Group() {
}
}
User.java
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Column(name="name")
private String name;
#NotBlank
#Column(name="email")
private String email;
#JsonIgnore
#ManyToMany(mappedBy = "usersOfgroup")
public Set<Group> memberInGroups =new HashSet<>();
public User() {
}
localhost:8080/view-group/groupName
{
"id": 1,
"groupName": "Group1",
"usersOfgroup": [
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
]
}
localhost:8080/view-user/Abhishek#abc.com
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
}
Expected response :
{
"id": 2,
"name": "Abhishek",
"email": "Abhishek#abc.com",
"memberInGroups":[
{
"id": 1,
"groupName": "Group1",
}
]
}
You have added #JsonIgnore on public Set<Group> memberInGroups =new HashSet<>();, thats why the json response doesn't have the data for this. Remove the annotation and you will see the expected response
The #JsonIgnore annotation is used to ignore the logical property used in serialization and deserialization.
You can go with below. It will address your immediate need for this specific case. Usually for a many-to-many, bi-directional relationship with lists, usually the solution is to decide which side of your relation is dominant and to have a JsonManagedReference and JsonBackReference combo, or use #JsonIdentityInfo if you have a list. Below is very good read on bidirectional cases to avoid infinite loops..
https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion
Coming to the solution I am referring to, you will have to override the getter of the list attribute and also use a #JsonInclude
In your Group class - use #JsonIgnore as shown and also put the getter as below to manually kill the loop
#ManyToMany
#JoinTable(name = "group_user", joinColumns = { #JoinColumn(name = "group_id") }, inverseJoinColumns = {
#JoinColumn(name = "user_id") })
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<User> usersOfgroup = new HashSet<>();
public Set<User> getUsersOfgroup() {
return this.usersOfgroup.stream().map(user -> {
user.memberInGroups = new HashSet<>();
return user;
}).collect(Collectors.toSet());
}
and in your User class, too do the same.
#ManyToMany(mappedBy = "usersOfgroup")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Group> memberInGroups = new HashSet<>();
public Set<Group> getMemberInGroups() {
return this.memberInGroups.stream().map(group -> {
group.usersOfgroup = new HashSet<>();
return group;
}).collect(Collectors.toSet());
}
I have an use case where a single user can have multiple houses. This is how the models look like
ApplicationUser.java
#Getter
#Setter
#Entity
public class ApplicationUser {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
#Column(nullable = false)
private String emailId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "applicationUser", cascade = CascadeType.REMOVE)
private List<House> houses;
}
House.Java
#Getter
#Setter
#Entity
public class House {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#ManyToOne()
#JoinColumn(name = "application_user")
private ApplicationUser applicationUser;
#Column(name = "House_Name")
private String houseName;
}
HouseRepository
public interface HouseRepository extends JpaRepository<House, Long> {
public House findByHouseName(String houseName);
public List<House> findAllByApplicationUser_Username(String userName);
}
Whenever I try to retrieve any house, the house object contains the user object and the user object again contains the house object. This goes on infinitely.
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
{
"id": 3,
"applicationUser": {
"id": 2,
"username": "test",
"emailId": "testmail",
"houses": [
How do I stop that from happening?
Since your House has an ApplicationUser and your ApplicationUser has a list of Houses, you've defined the classes to be circular.
Odds are that your Object Oriented model is 100% identical to the database model. This is probably a bad idea; as in a model of the home application, you wouldn't generally hold the application within the user embedded within the application.
Read What is “the inverse side of the association” in a bidirectional JPA OneToMany/ManyToOne association? for more details.
I have a problem working with foreign keys in Spring. The returned object doesn't have the correct value on my relation ship.
The orderTypeId of an Order is missing when in the CREATED response but a subsequent GET request yields the expected result.
Expected behavior
Here's a small example of what I want to achieve.
Request
POST /api/orders
{
"orderType": "orderTypes/1",
}
or GET /api/orders/2
Output
{
"id": 2,
"orderTypeId": 1, // either this
"orderType": "orderTypes/1" // or this
"_links": {
"self": {
"href": "http://localhost:8090/api/orders/2"
},
"order": {
"href": "http://localhost:8090/api/orders/2"
},
"orderType": {
"href": "http://localhost:8090/api/orders/2/orderType"
},
"orderType": {
"href": "orderTypes/1" // this could even be acceptable
}
}
}
My code
Entites
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#Column(name = "order_type_id", nullable = false, insertable = false, updatable = false)
private Long orderTypeId;
#NotNull
#ManyToOne
#JoinColumn(name = "order_type_id")
private OrderType orderType;
}
#Data
#Entity
public class OrderType {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
#JsonIgnore
#OneToMany(mappedBy = "orderType")
private List<Order> orders;
}
Controller
#RepositoryRestResource
public interface OrderRepository extends JpaRepository<Order, Long> {}
POST
POST /api/orders
{
"orderType": "orderTypes/1",
}
Output
{
"id": 2,
"orderTypeId": null,
"_links": {
"self": {
"href": "http://localhost:8090/api/orders/2"
},
"order": {
"href": "http://localhost:8090/api/orders/2"
},
"orderType": {
"href": "http://localhost:8090/api/orders/2/orderType"
}
}
}
However if I do GET /orders/2 orderTypeId is correctly set.
What am I doing wrong?
UPDATE
I tried something else
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#NotNull
#ManyToOne
#JoinColumn
#JsonManagedReference
#RestResource(exported=false) // ADDED
private OrderType orderType;
}
#Data
#Entity
public class OrderType {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
// removed orders
}
GET /api/orders/2
{
"id": 2,
"orderTypeId": {
"id": 1,
"name": "foo"
},
"_links": {
"self": {
"href": "http://localhost:8090/api/orders/2"
},
"order": {
"href": "http://localhost:8090/api/orders/2"
}
}
}
But now POST doesn't work :(
POST /api/orders
{
"orderType": "orderTypes/1",
}
Returns 400 Bad Request...
Using #JsonIgnore is not the recommended way, to manage cyclic dependency please use #JsonManagedReference in your entities.
Entities:
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#Column(name = "order_type_id", nullable = false, insertable = false, updatable = false)
private Long orderTypeId;
#JsonBackReference(value="name")
#ManyToOne
#JoinColumn(name = "order_type_id")
private OrderType orderType;
}
#Data
#Entity
public class OrderType {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String name;
#JsonManagedReference(value = "name")
#OneToMany(mappedBy = "orderType")
private List<Order> orders;
}
Now to answer your question, the return of post API will not return OrderType as Hibernate does not recognise it as an initialised entity.
You can either do a GET call later as happening in your case already or have a rest controller in a POST api after saving you can do a get call by id before returning or use a DTO for sending response.
The problem is that this field will not be populated when a new instance is constructed i.e. as a result of a POST request. It will however be populated in response to a GET request i.e. it will be populated by Hibernate as a result of the JPA annotations on the field.
I am not quite sure why you would need to map this as a persistent field. Simply adding a getter should mean it is included in any JSON response:
#Data
#Entity
public class Order {
#Id
#GeneratedValue
private Long id;
#NotNull
#ManyToOne
#JoinColumn(name = "order_type_id")
private OrderType orderType;
public Long getOrderTypeId(){
return orderType != null ? orderType.getId() : null;
}
}
Failing that you could add a listener for an AfterCreateEvent
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#events
and either set the value manually on this listener based on the associated OrderType or, inject an EntityManager to your listener and trigger a refresh on the saved instance:
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#refresh(java.lang.Object)
#RepositoryEventHandler
public class OrderEventHandler {
#PersistenceContext
private EntityManager em;
#HandleAfterCreate
public void onOrderCreated(Order o) {
em.refresh(o);
}
}
I'm having a weird problem with Jackson serialization - I have a Role entity have a nested Permission entity which, in turn, contains a nested Metadata entity. When these entities are retrieved from a Spring MVC #RestController as a list, Jackson serializes the Permission collection into a JSON array. The problem is that sometimes the element placed in this array is just the id of the Permission rather than a serialized representation of the object.
Role.class:
#Entity
#Table(name = "t_db_roles")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Role.class)
public class Role implements GrantedAuthority {
private final static Logger log = LoggerFactory.getLogger(Permission.class);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "auto_id")
private int id;
#Column(name = "role", length = 50)
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "t_db_role_permissions",
joinColumns = {#JoinColumn(name = "roleid", referencedColumnName = "auto_id")},
inverseJoinColumns = {#JoinColumn(name = "permid", referencedColumnName = "auto_id")}
)
private Set<Permission> permissions;
// getters and setters omitted
}
Permission.class:
#Entity
#Table(name = "t_db_permissions")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Permission.class)
public class Permission implements GrantedAuthority {
private final static Logger log = LoggerFactory.getLogger(Permission.class);
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "auto_id")
private int id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy = "permission")
private Metadata metadata;
}
Metadata.class
#Entity
#Table(name = "t_report_data")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Metadata.class)
public class Metadata {
#Id
#Column(name = "id", insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "file_name")
private String fileName;
#Column(name = "human_name")
private String humanName;
#Column(name = "links_to")
#JsonIgnore
private Integer linksTo;
#Column(name = "is_subreport")
#JsonIgnore
private Boolean isSubreport;
#OneToOne(cascade = javax.persistence.CascadeType.ALL, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "permid")
private Permission permission;
}
The controller:
#RestController
public class RoleRestController {
private final static Logger log = LoggerFactory.getLogger(PermissionRestController.class);
private RoleService roleService;
private MetadataService metadataService;
#Autowired
public void setRoleService(RoleService service) {
this.roleService = service;
}
#Autowired
public void setMetadataService(ReportMetadataService service) { this.metadataService = service; }
#RequestMapping(value = "/admin/roles/", method = RequestMethod.GET)
public List<Role> getRoles() {
return roleService.getRoles();
}
}
I'm fairly sure that the problem is in serialization - echoing the List<Role> to the console works as expected, but here is the JSON returned (note the first element of the permissions array is an integer rather than a JSON object):
{
"id": 10,
"name": "ROLE_TESTER",
"permissions": [
14,
{
"id": 7,
"name": "ViewDailySettlementSummaryGL",
"metadata": {
"id": 41,
"fileName": "acct_summary_gl.rptdesign",
"humanName": "Daily Settlement Summary GL",
"permission": 7
},
"authority": "ViewDailySettlementSummaryGL"
},
{
"id": 6,
"name": "ViewDailySettlementSummary",
"metadata": {
"id": 24,
"fileName": "acct_summary_os.rptdesign",
"humanName": "Daily Settlement Summary",
"permission": 6
},
"authority": "ViewDailySettlementSummary"
}
],
"authority": "ROLE_TESTER"
}
I can work around this by handling Role serialization manually, but since the SpringMVC/Jackson serialization works for other classes in the project it seems like there must be a problem in these classes that i'm overlooking. Any ideas?