Save at the same time an composed object using JpaRepository - java

Can I save an object that contains another object directly in the database?
My back-end structure is like this:
Rest services
Services
Repository (extends JpaRepository)
Model
Suppose that I have two entity in my model: Company and Address. Both generated by the JPA tool provided by IntelliJ.
Company class model
#Entity
public class Company {
private int idcompany;
private String name;
private Address address;
#Id
#Column(name = "idcompany")
public int getIdcompany() {
return idcompany;
}
public void setIdcompany(int idcompany) {
this.idcompany = idcompany;
}
#Basic
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToOne
#JoinColumn(name = "idaddress", referencedColumnName = "idaddress", nullable = false)
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
}
Address class model
#Entity
public class Address {
private long idaddress;
private String zip;
#Id
#Column(name = "idaddress")
public long getIdaddress() {
return idaddress;
}
public void setIdaddress(long idaddress) {
this.idaddress = idaddress;
}
#Basic
#Column(name = "zip")
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
}
Moreover, both entities have an interface that extends the interface JpaRepository<T,ID>. So, I have CompanyRepository and AddressRepository. I use these two interfaces in they respective Service classes: CompanyService and AddressService. This is where I put the business logic. All ok!
Now, I recive using a REST service, through POST an object Company that contains the object Address. I need to save them into the database (MySql).
In the json file there are Company that contains Address!
Until now, I've always done these steps:
Save Address;
Retrieve the Address just saved (i need the idaddress);
I associate the Address to the company using setAddress;
Save Company
I tried to save the object Company received via REST calling the method save from CompanyService (using CompanyRepository) but I got the error:
Column 'idaddress' cannot be null
I ask you. Is there an easier way to save Company and Address at the same time using JpaRepository???

You don't have defined any cascading.
You could define PERSIST to let the Address persist when Company is persisted:
#ManyToOne
#JoinColumn(name = "idaddress", referencedColumnName = "idaddress", nullable = false,
cascade = CascadeType.PERSIST)
public Address getAddress() {
return address;
}
For every method on the EntityManager there is a CascadeType defined.
Read more about the cascade types here:
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/

Related

Spring - POSTing JSON body with nested entity does not work

I'm new to spring and java, so apologises if this is obvious.
TLDR
When I send JSON with nested resources, it creates the subresource. EVEN when it already exists, causing a persistence issue, how do you stop this in Spring?
I have two entities, Book and Shelf. A shelf can have multiple books but a book can only be on one shelf. So Shelf (1) <-> (*) Book.
#Entity
public class Book {
#Id
#GenericGenerator(name = "uuid-gen", strategy = "uuid2")
#GeneratedValue(generator = "uuid-gen")
#Type(type = "pg-uuid")
private UUID id;
#Column(nullable = false)
private String name;
private String description;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(foreignKey = #ForeignKey(name = "shelf_id"))
private Shelf shelf;
public Book() {
}
public Book(UUID id, String name, String description, Shelf shelf) {
this.id = id;
this.name = name;
this.description = description;
this.shelf = shelf;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setShelf(Shelf shelf) {
this.shelf = shelf;
}
}
Shelf
#Entity
public class Shelf {
#Id
#GenericGenerator(name = "uuid-gen", strategy = "uuid2")
#GeneratedValue(generator = "uuid-gen")
#Type(type = "pg-uuid")
private UUID id;
#Column(nullable = false)
private String name;
private String description;
#OneToMany(fetch=FetchType.LAZY, mappedBy = "shelf", cascade = CascadeType.ALL)
private Set<Book> books;
public Dashboard() {
}
public Dashboard(UUID id, String name, String description, Set<Book> books) {
this.id = id;
this.name = name;
this.description = description;
this.book = book;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setBooks() {
for (Book book : books)
book.setShelf(this);
}
public Set<Book> getBooks() {
return books;
}
}
I have a ShelfRepository which extends JpaRepository.
When I make a request with the body
{"name":"ShelfName", "books": [{"name": "bookName"}]}
it will return create the resource Book and Shelf but does not link them as Book is created first without Shelf to reference. So calling setBooks on the Shelf is needed. Not ideal but I cant figure out another way.
Creating a book and using the id as the reference in the books array (which is what I would like in my API) like below:
{"name":"otherShelfName", "books": [{"id": "7d9c81c2-ac25-46ab-bc4d-5e43c595eee3"}]}
This causes a persistence issue as the book already exist
org.hibernate.PersistentObjectException: detached entity passed to persist
Is there a way to have a nested resource like above in Spring and be able to associate without a persistence issue?
-------- Services
#Service
public class BookService {
#Autowired
private BookRepository bookRepository;
public List<Book> findAll() {
return bookRepository.findAll();
}
public Book create(Book book) {
return bookRepository.save(book);
}
}
#Service
public class ShelfService {
#Autowired
private ShelfRepository shelfRepository;
public List<Shelf> findAll() {
return shelfRepository.findAll();
}
public Book create(Book book) {
Shelf newShelf = shelfRepository.save(shelf);
shelf.setBooks();
return newShelf;
}
}
Welcome to the painful ... ehhh I meant wondeful ... world of ORMs where doing simple things is trivial, but doing things even a tad complicated becomes increasingly complicated :P
Few pointers that might help:
If a book HAS to belong to a shelf, the #JoinColumn should probably also have nullable=false; this should force Hibernate to persiste the shelf first since it will need the ID to persist the book's shelf_id FK.
You have a bidirectional relationship which, in a way, can be seen as an infinite loop (shelf contains books, that references shelf, that contains books, that ...); there are ways to handle that using Jackson, you can read about it here.
Going back to both points above, although your shelf contains books in your JSON data, that same data doesn't explicitly have the book point back to shelf AND, from an Hibernate perspective the relationship is optional anyway so it probably just didn't bother doing the link.
Now the "nullable=false" trick might solve all of that if you are lucky, but nothing is ever easy so I would doubt it; as I said before, if you look only at the JSON for books, they have no references to the parent shelf so when Jackson converts the books to Java object, the "shelf" property most probably stays null (but you cannot reference the shelf's ID since it wasn't created yet ... how fun!). What you will have to do is, in your ShelfRepository, when you create the shelf, you will first have to go through all the books it contains and set the reference to the parent shelf as explained in this article.
Finally, regarding the "detached entity passed to persist" exception, Hibernate will always do that if you give it an object with its identity field populated, but the actual object was never fetched using Hibernate; if you are merely referencing an object in your JSON using the ID, you will have to first fetch the real entity from Hibernate and use that exact instance in your persistence.
I hope all this info will help you.

Sending objects with mockito perform

Hello I'm trying to test method:
#RequestMapping(value="addOwner", method = RequestMethod.POST)
public String addOwnerDo(#Valid #ModelAttribute(value = "Owner") Owner owner, BindingResult result) {
if (result.hasErrors()){
return "addOwner";
}
ownerService.add(owner);
return "redirect:addOwner";
}
so I wrote this unit test case:
#Test
public void testAddOwnerPost() throws Exception{
mockMvc.perform(post("/addOwner")
.param("firstName", "Adam")
.param("lastName", "Kowalski")
.requestAttr("pet", new Pet())
.requestAttr("phone", new Phone()))
.andExpect(status().is3xxRedirection());
}
the problem is, that Owner entity contains fields that are type of Pet and Phone:
#Entity
public class Owner {
#Id
#GeneratedValue
private int id;
#Column(name = "first_name")
#Size(min=2,max=15,message="{Size}")
private String firstName;
#Column(name = "last_name")
#Size(min=2,max=15,message="{Size}")
private String lastName;
#Valid
#NotNull(message="{NotNull}")
#OneToOne(cascade = CascadeType.ALL,orphanRemoval=true)
private Pet pet;
#Valid
#NotNull(message="{NotNull}")
#OneToOne(cascade = CascadeType.ALL, orphanRemoval=true)
private Phone phone;
So the object beeing send to controller while testing has values of Pet and Phone equal to null. And I would like to send object that has those fields set.
I suggest you to do the following:
On your test class create two attributes :
private Pet pet = new Pet();
private Phone phone = new Phone();
Before your public void testAddOwnerPost() create a new method : #Before
public void setUp()
And then set all of our attribute inside the setUp class.
If you don't care about the value of your phone and pet I suggest you to use Mockito.anyString() // for a string value
O

Returning JSON via REST for a #OneToMany fails with (formerly NullPointer) Exception

I have a simple proof-of-concept demo using Spring Data REST / RestRepository architecture. I started with two entities:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="Address")
public class Address implements Serializable {
public Address() {}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_ADDRESS_ID_GENERATOR", strategy="native")
private int ID;
//#RestResource(exported = false)
#ManyToOne(targetEntity=domain.location.CityStateZip.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.PERSIST})
#JoinColumns({ #JoinColumn(name="CityStateZipID", referencedColumnName="ID", nullable=false) })
private domain.location.CityStateZip cityStateZip;
#Column(name="StreetNo", nullable=true)
private int streetNo;
#Column(name="StreetName", nullable=false, length=40)
private String streetName;
<setters and getters ommitted>
}
and for CityStateZip:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="CityStateZip")
public class CityStateZip {
public CityStateZip() {}
#Column(name="ID", nullable=false, unique=true)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_CITYSTATEZIP_ID_GENERATOR", strategy="native")
private int ID;
#Column(name="ZipCode", nullable=false, length=10)
private String zipCode;
#Column(name="City", nullable=false, length=24)
private String city;
#Column(name="StateProv", nullable=false, length=2)
private String stateProv;
}
with repositories:
#RepositoryRestResource(collectionResourceRel = "addr", path = "addr")
public interface AddressRepository extends JpaRepository<Address, Integer> {
List<Address> findByStreetNoAndStreetNameStartingWithIgnoreCase(#Param("stNumber") Integer streetNo, #Param("street") String streetName);
List<Address> findByStreetNameStartingWithIgnoreCase(#Param("street") String streetName);
List<Address> findByStreetNo(#Param("streetNo") Integer strNo);
}
and:
// #RepositoryRestResource(collectionResourceRel = "zip", path = "zip", exported = false)
#RepositoryRestResource(collectionResourceRel = "zip", path = "zip")
public interface CityStateZipRepository extends JpaRepository<CityStateZip, Integer> {
List<CityStateZip> findByZipCode(#Param("zipCode") String zipCode);
List<CityStateZip> findByStateProv(#Param("stateProv") String stateProv);
List<CityStateZip> findByCityAndStateProv(#Param("city") String city, #Param("state") String state);
}
and main() code of
#Configuration
#EnableJpaRepositories
#Import(RepositoryRestMvcConfiguration.class)
#EnableAutoConfiguration
// #EnableTransactionManagement
#PropertySource(value = { "file:/etc/domain.location/application.properties" })
#ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
with this code, things work fine. I can save a CityStateZip and reference it while saving an Address, and can GET the resulting address. Step two was to add another class: An Address can have 0-to-many associated Remarks, while a Remark is tied to a single Address. That entity is:
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="Remark")
#SuppressWarnings({ "all", "unchecked" })
public class Remark implements Serializable {
public Remark() {
}
#Column(name="ID", nullable=false)
#Id
#GeneratedValue(generator="CUSTOMER_ADDRESSES_REMARK_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="CUSTOMER_ADDRESSES_REMARK_ID_GENERATOR", strategy="native")
private int ID;
#ManyToOne(targetEntity=domain.location.Address.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="LocationID", referencedColumnName="ID", nullable=false) })
private domain.location.Address address_ix;
#Column(name="Comment", nullable=false, length=255)
private String comment;
<getters, setters removed>
}
and a repository:
#RepositoryRestResource(collectionResourceRel = "remarks", path = "remarks")
public interface RemarkRepository extends JpaRepository<Remark, Integer> {
}
and I add this to Address:
#OneToMany(mappedBy="address_ix", targetEntity=domain.location.Remark.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.Set remark = new java.util.HashSet();
I can retrieve the exposed API by doing GET .../addr if I do not have any Address instances. I can still save an Address instance to the database. But once I have a saved instance, doing a GET .../addr, or trying to GET the specific instance, I get an exception:
"timestamp": 1418423263313,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write JSON: (was java.lang.NullPointerException) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: org.springframework.hateoas.PagedResources[\"_embedded\"]->java.util.UnmodifiableMap[\"addr\"]->java.util.ArrayList[0])",
"path": "/addr"
I assume I am doing something wrong with the #OneToMany association in Address, or the #ManyToOne in Remark, or a combination of both. I have added a #ManyToOne, one-directional association to Address with no problems. What do I need to do to be able to retrieve Address data through GETs? What is JSON complaining about? (At this point, there was no Remark instance in the database. I even added a #JsonIgnore annotation in front of the #OneToMany, but I still got the error.
It turns out that the problem was due to auto-generated code. The declaration of the Set of remarks is missing the parameter calling out the type of Set (or HashSet). If I change the code to
#OneToMany(mappedBy="address_ix", targetEntity=domain.location.Remark.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.Set<Remark> remark = new java.util.HashSet<Remark>();
the code now works as required. Without the parameter, the (Spring or JSON) code did not know how to process this member.

org.hibernate.ObjectNotFoundException issue with using list()

The following query throws the exception:
Query query = session.createQuery("from Associate as a order by a.username asc");
associates = query.list();
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [ca.mypkg.model.Associate#0]
If I create an entry in the database with id of 0 it works just fine. I don't really get it because I'm just trying to load all the entries in the db not just a specific one.
Similar questions I've found have been concerned with trying to load an object with a given ID I'm doing no such thing.
Associate class:
#Table(name = "user")
#XmlRootElement(name = "associate")
public class Associate implements Serializable {
private String username;
private String password;
private String firstName;
private String lastName;
private String userType;
private int id;
private String email;
private String isActive;
private Department dept;
private String lastUpdated;
private String associate_type;
// ...
#Id
#GeneratedValue
public int getId() {
return id;
}
#OneToOne
#JoinColumn(name = "dept")
public Department getDept() {
return dept;
}
From my experience this type of error message usually means it does not find joined entity by mentioned id, and not the entity requested in the query (Associate, in your case).
My guess is that Associate class contains a join entity which has primitive type primary key.

Bind Stringattribute to Column of separate Table

is it possible to bind a String attribute of an JavaClass to a column of a different table.
Example
#Entity
#Table(name = "ACCOUNTS")
public class Account {
private Long id;
private String nickname;
private String address;
#Id
#Column(name = "A_ID")
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name="A_NICKNAME")
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
What would be the correct annotation if the address where a String in a separate table?
Separate Table
CREATE TABLE IF NOT EXISTS `Adresses` (
`ad_id` BIGINT NOT NULL AUTO_INCREMENT ,
`ad_address` VARCHAR(45) NOT NULL ,
PRIMARY KEY (`da_id`) )
ENGINE = InnoDB;
thanks
You can't define address field of your object to be stored in another table (it has its own identifier that you don't have it in your account class ) but you can define another class for address and let it have its own mapping and table then your account class will have a many to one relationship with address class.This approach can have advantages too.What if you want to add another fields to your address table ? and if there's no chance that other fields can be added to your address table why do you want to store it in a separate table ?

Categories

Resources