I try to start with a simple spring data rest application and struggle around with multiple OneToMany relations. I have a person which has many items and many addresses. To get the application up and running I have to set the #RestResource annotation in items and address to create the correct links. But unfortunately the link doesn't work (I get an empty result) and why can't I set the #RestResource annotation in the person entity. Here is my code. I hope you can help me.
Person:
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private int id;
private String name;
//bi-directional many-to-one association to Item
#OneToMany
#JoinColumn(name="id")
private List<Item> items;
//bi-directional many-to-one association to Address
#OneToMany
#JoinColumn(name="id")
private List<Address> addresses;
public Person() {
}
// getter setter
Address
#Entity
public class Address implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private int id;
private String address;
//bi-directional many-to-one association to Person
#ManyToOne
#JoinColumn(name="person_id")
#RestResource(rel="address", path="address",exported=true)
private Person person;
public Address() {
}
// getter setter
Item
#Entity
#Table(name="items")
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private int id;
#Column(name="item_name")
private String itemName;
//bi-directional many-to-one association to Person
#ManyToOne
#JoinColumn(name="person_id")
#RestResource(rel="item", path="item",exported=true)
private Person person;
public Item() {
}
// getter setter
And I get this result
{
"name" : "dave",
"items" : [ {
"itemName" : "ite,m"
} ],
"addresses" : [ {
"address" : "myaddress1"
} ],
"_links" : {
"self" : {
"href" : "http://localhost:8090/person/1"
},
"person" : {
"href" : "http://localhost:8090/person/1"
},
"address" : {
"href" : "http://localhost:8090/person/1/address"
},
"item" : {
"href" : "http://localhost:8090/person/1/item"
}
}
}%
Related
This question is already phrased as an issue here: https://github.com/spring-projects/spring-data-jpa/issues/2369 but for lack of a reaction there I am copying the contents of that issue here, hoping that somebody might find what's wrong with my code or confirm that this could be a bug:
I've set up an example project here that showcases what seems to be a bug in Spring Data projections: https://github.com/joheb-mohemian/gs-accessing-data-jpa/tree/primary-key-join-column-projection-bug/complete
I have a Customer entity that has a OneToOne mapping to an Address entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "customer", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address address;
//...
}
#Entity
public class Address {
#Id
#Column(name = "customer_id")
private Long id;
#OneToOne
#MapsId
#JoinColumn(name = "customer_id")
private Customer customer;
private String street;
//...
}
Then there are simple projection interfaces:
public interface CustomerProjection {
String getFirstName();
String getLastName();
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreet();
}
But when I try to fetch a projected entity from a repository method like this one:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
//...
<T> T findById(long id, Class<T> type);
}
, getAddress() on the projection will be null, whereas getAddress() when fetching the entity type is populated correctly. Of these two unit tests, only testEntityWithOneToOne()will be successful:
#BeforeEach
void setUpData() {
customer = new Customer("first", "last");
Address address = new Address(customer, "street");
customer.setAddress(address);
entityManager.persist(address);
entityManager.persist(customer);
}
#Test
void testEntityWithOneToOne() {
Customer customerEntity = customers.findById(customer.getId().longValue());
assertThat(customerEntity.getAddress()).isNotNull();
}
#Test
void testProjectionWithOneToOne() {
CustomerProjection customerProjection = customers.findById(customer.getId(), CustomerProjection.class);
assertThat(customerProjection.getAddress()).isNotNull();
}
What's the problem here?
I'm studying bidirectional mapping.
I mapped Team and Member with #OneToOne and #ManyToMany annotations.
#Entity
public class Team {
#Id #GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "team")
private List<Member> members=new ArrayList<Member>();
//omit getter, setter ,toString
}
#Entity
public class Member {
#Id
#GeneratedValue
private Long id;
#Column(name="USERNAME")
private String name;
#ManyToOne
#JoinColumn(name="TEAM_ID")
private Team team;
#Enumerated(EnumType.STRING)
private Status status;
//omit getter, setter , toString
}
main method
public static void main(String args[]){
//...
Team team= new Team();
team.setName("RedTeam");
em.persist(team);
Member member= new Member();
member.setName("me");
member.setStatus(Status.ADMIN);
member.setTeam(team);
em.persist(member);
Member findmember= em.find(Member.class, member.getId());
Team findTeam= findmember.getTeam();
System.out.println("members: "+findTeam.getMembers());
//...
}
results:
members: []
I wonder why "members" were not added to the "members field" of "Team" in the code above.
Thank you in advance.
These are the minimal changes to make it to work
#Entity
public class Team {
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private #Id Long id;
private String name;
#OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Member> members = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String pName) {
name = pName;
}
public void addMember(Member m) {
getMembers().add(m);
m.setTeam(this);
}
public void removeMember(Member m) {
getMembers().remove(m);
m.setTeam(null);
}
public List<Member> getMembers() {
return members;
}
}
Team team = new Team();
team.setName("RedTeam");
// em.persist(team);
Member member = new Member();
member.setName("" + new Random().nextInt(999999));
team.addMember(member);
em.persist(team);
Member findmember = em.find(Member.class, member.getId());
Team findTeam = findmember.getTeam();
System.out.println("Members: " + findTeam.getMembers());
I've got some difficulties with my JPA Rest Project.
I have build my repositories for each of my entity (my tables in my database), and it works fine.
For example, a part of my entity "Personne" :
#Entity
public class Personne {
private Long id;
private String nom;
private String prenom;
private Date dateNaissance;
private String telDomicile;
private String telPortable;
private String telAutre;
private String telCommentaire;
private String fax;
private String mail;
private String commentaire;
private Timestamp dateSuppr;
private String sexe;
private Patient patientById;
private Adresse adresseByAdresseId;
#Id
#JsonProperty(value = "dataId")
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
And myRepository with a #Query :
#Transactional
#RepositoryRestResource(collectionResourceRel = "personne", path = "personne", excerptProjection = InlinePersonne.class)
public interface PersonneRepo extends JpaRepository<Personne, Long> {
#Query("from Personne p where p.nom = ?1 and p.prenom = ?2")
public Personne customRequest(String nom, String prenom);
}
My problem : the return result is always a type "Personne".
I would like to make a native request that sends me back an object, with customized properties.
Example of the wished return :
{object :
{name : String,
surname : String,
age : int },
adresse :{
city : String,
street : String
}
}
Is it possible to do that ?
I really need it because I have to make complex requests on many tables.
Thank you.
You could use interface-base projections:
First you create interfaces that reflect the fields you need:
interface PersonSummary {
String getName();
String getSurename();
int getAge();
AddressSummary getAddress();
interface AddressSummary {
String getCity();
String getStreet();
}
}
Then you indicate your custom query what interface it needs to extend and instantiate to populate the information:
public interface PersonneRepo extends JpaRepository<Personne, Long> {
// All your other abstract method
// Brand new query
#Query("Select p.name, p.surname, p.age, p.city, p.street from Personne p where p.nom = ?1 and p.prenom = ?2")
public PersonSummary customRequest(String nom, String prenom);
}
You would be receiving an object like this:
{
name : String,
surname : String,
age : int,
address :{
city : String,
street : String
}
}
You would need to test how flexible is this functionality in the terms of the composition complexity of the object you want to receive.
i have this Rest-DSL:
// this api creates new user
rest("/user")
.post()
.type(User.class).to("jpa://com.project.User")
This is my entities:
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user")
private List<User> users;
}
my problem is in my swagger in the Body value parameter example. It contains like this:
{
"id": "string",
"role": {
"id": "string",
"users": [
{
"id": "string",
"roles": [
{}
]
}
]
}
}
quite complicated, although i need only id and id_role parameters to create (POST) new user. I hope the body example shows like this:
{
"id": "string",
"id_role": "string"
}
I realized that my entities are not created properly. These was i learned:
Configure CascadeType in associated JPA entities
#Entity
public class User {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
private List<User> users;
}
to make class not recursive, set #JsonIgnore
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class User{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "id_role")
private Role role;
}
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Role{
#Id
private String id;
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
#JsonIgnore
// this attribute will not appear inside Role class
private List<User> users;
}
I have an interesting problem. My data-model is the following:
Type A:
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
Type B:
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
Embeddable C:
#Embeddable
#JsonIgnoreProperties(ignoreUnknown = true)
public class C {
#ManyToOne
private A a;
#ManyToOne
private B b;
}
And Type D:
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
public class D {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ElementCollection
#OrderColumn(name = "ORDER_INDEX")
#CollectionTable(
name = "d_c_join",
joinColumns = #JoinColumn(name = "d_id")
)
private List<C> listOfC;
}
Deserialization (and storing) the entities works fine. When an object of class D is serialized the following is the outcome:
{
"_embedded" : {
"ds" : [ {
"id" : 1,
"listOfC" : [ { }, { } ],
"_links" : {
"self" : {
"href" : "http://localhost:8000/ds/1"
}
}
} ]
}
}
How can I configure Spring-Data to serialize A and B in C (best would be by their URI).
I'm pretty positive what you're looking for are Projections. I don't think Spring will serialize referenced collections without it.
See my answer here for more details: Spring Data-Rest POST to sub-resource