Can't remove entity with JPA - java

I'm trying to remove an entity from memory (at the moment I don't use DB) with JPA, when I use remove and then try to find the deleted entity it shows null, but when I use findAll method it retrieve all data (with removed entity)...
Profile.java
#Entity
#Table(name = "profile")
public class Profile {
#Id
#GeneratedValue
private Long id;
private String nombre;
private Boolean restrictedAccess;
private Boolean canValidate;
// private Set<AccessField> accessFields = new HashSet<AccessField>();
// private Set<AccessEntity> accessEntities = new HashSet<AccessEntity>();
#OneToMany(mappedBy = "profile", fetch = FetchType.EAGER)
private Set<AccessMenu> menuSections = new HashSet<AccessMenu>();
#OneToMany(mappedBy = "profile", fetch = FetchType.EAGER)
private Set<User> users = new HashSet<User>();
[getters and setters]
ProfileRepository
#Repository
#Transactional
public class ProfileRepository {
#PersistenceContext
private EntityManager entityManager;
public Profile save(Profile p) {
p = this.entityManager.merge(p);
this.entityManager.flush();
return p;
}
public void delete(Long id){
Profile profile = this.entityManager.find(Profile.class, id);
this.entityManager.remove(profile);
}
public List<Profile> findAll() {
CriteriaQuery cq = this.entityManager.getCriteriaBuilder().createQuery();
cq.select(cq.from(Profile.class));
return (List<Profile>) this.entityManager.createQuery(cq).getResultList();
}
public Profile findById(Long id){
return this.entityManager.find(Profile.class, id);
}
}
Controller method
#RequestMapping(value="profile/delete/{idProfile}", method = RequestMethod.GET)
public String delete(#PathVariable String idProfile,RedirectAttributes ra, Model model){
profileRepo.delete(Long.valueOf(idProfile));
model.addAttribute("profiles", profileRepo.findAll());
return "profile/list";
}

if you are are trying to delete an entity by using Id in the controller, do it like profileRepo.deleteById(Long.valueOf(idProfile));
this, not like this profileRepo.delete(profileRepo.findById(Long.valueOf(idProfile)));
Also use your repository functions like these,
public void deleteArtistById(Long artistId) {
Artist artist = manager.find(Artist.class, artistId);
if (artist != null) {
manager.getTransaction().begin();
manager.remove(artist);
manager.getTransaction().commit();
}
}
public void deleteArtist(Artist artist) {
manager.getTransaction().begin();
manager.remove(artist);
manager.getTransaction().commit();
}
You can take a look at this link for more detail:
http://kodejava.org/how-do-i-delete-entity-object-in-jpa/

At last I found a solution, the problem was when I tried to remove Profile, users and menuSections have related data, so finally I put in menuSections a cascade = CascadeType.REMOVE and in users set profile property to null

Related

#Transactional does not work as expected, because the "save" method is needed to save to the database

#Transactionalshould itself reflect the changes made to the entity in the database.
I'm creating an application where the client can create a Car entity that looks like this (the update method is later used by PUT, do not pay attention to the brand property):
#Entity
#Table(name = "cars")
public class Car {
#Id
#GeneratedValue(generator = "inc")
#GenericGenerator(name = "inc", strategy = "increment")
private int id;
#NotBlank(message = "car name`s must be not empty")
private String name;
private LocalDateTime productionYear;
private boolean tested;
public Car() {
}
public Car(#NotBlank(message = "car name`s must be not empty") String name, LocalDateTime productionYear) {
this.name = name;
this.productionYear = productionYear;
}
#ManyToOne
#JoinColumn(name = "brand_id")
private Brand brand;
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionYear() {
return productionYear;
}
public void setProductionYear(LocalDateTime productionYear) {
this.productionYear = productionYear;
}
public boolean isTested() {
return tested;
}
public void setTested(boolean tested) {
this.tested = tested;
}
public Brand getBrand() {
return brand;
}
void setBrand(Brand brand) {
this.brand = brand;
}
public Car update(final Car source) {
this.productionYear = source.productionYear;
this.brand = source.brand;
this.tested = source.tested;
this.name = source.name;
return this;
}
}
In my application, the client can create a new Car or update an existing one with the PUT method.
My controller:
#RestController
public class CarController {
private Logger logger = LoggerFactory.getLogger(CarController.class);
private CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
//The client can create a new resource or update an existing one via PUT
#Transactional
#PutMapping("/cars/{id}")
ResponseEntity<?> updateCar(#PathVariable int id, #Valid #RequestBody Car source) {
//update
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> {
car.update(source); //it doesn`t work
//Snippet below works
//var updated = car.update(source);
//repository.save(updated);
});
return ResponseEntity.noContent().build();
}
//create
else {
var result = repository.save(source);
return ResponseEntity.created(URI.create("/" + id)).body(result);
}
}
}
When I create a new Car, it works. However as described in the code, when there is no save method the entity is not changed although I get the status 204 (no content). When there is a save method, it works fine.
Do you know why this is so?
One of the users asked me for a Brand entity. I haven't created any Brand object so far but essentially Car can belong to a specific Brand in my app. So far, no Car belongs to any Brand. Here is this entity:
#Entity
#Table(name = "brands")
public class Brand {
#Id
#GeneratedValue(generator = "i")
#GenericGenerator(name = "i", strategy = "increment")
private int id;
#NotBlank(message = "brand name`s must be not empty")
private String name;
private LocalDateTime productionBrandYear;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "brand")
private Set<Car> cars;
#ManyToOne
#JoinColumn(name = "factory_id")
private Factory factory;
public Brand() {
}
public int getId() {
return id;
}
void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getProductionBrandYear() {
return productionBrandYear;
}
public void setProductionBrandYear(LocalDateTime productionBrandYear) {
this.productionBrandYear = productionBrandYear;
}
public Set<Car> getCars() {
return cars;
}
public void setCars(Set<Car> cars) {
this.cars = cars;
}
public Factory getFactory() {
return factory;
}
public void setFactory(Factory factory) {
this.factory = factory;
}
}
I tried your entities with same use case locally and found out everything is working fine, I am writing here my findings and configurations so that you can verify what's going on wrong for you.
So, when I issue a PUT call providing id but Car entity doesn't exist into table, it gets created and I receive 201 response (I guess you are getting the same)
you can see that row with value got inserted into table as well
and these are the query logs printed
- [nio-8080-exec-8] org.hibernate.SQL: select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_ from car car0_ where car0_.id=?
[nio-8080-exec-8] org.hibernate.SQL: insert into car (brand_id, name, production_year, tested) values (?, ?, ?, ?)
Now, let's come to updating the same entity, when issued PUT request for same id with changed values notice that values changes in table and update queries in log
You can see that got same 204 response with empty body, let's look the table entry
So changes got reflected in DB, let's look at the SQL logs for this operation
select count(*) as col_0_0_ from car car0_ where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: select car0_.id as id1_1_0_, car0_.brand_id as brand_id5_1_0_, car0_.name as name2_1_0_, car0_.production_year as producti3_1_0_, car0_.tested as tested4_1_0_, brand1_.id as id1_0_1_, brand1_.name as name2_0_1_, brand1_.production_year as producti3_0_1_ from car car0_ left outer join brand brand1_ on car0_.brand_id=brand1_.id where car0_.id=?
[nio-8080-exec-1] org.hibernate.SQL: update car set brand_id=?, name=?, production_year=?, tested=? where id=?
So, I am not sure, how you verified and what you verified but your entities must work, I have used same controller function as yours
#RestController
class CarController {
private final CarRepository repository;
public CarController(CarRepository repository) {
this.repository = repository;
}
#PutMapping("/car/{id}")
#Transactional
public ResponseEntity<?> updateCar(#PathVariable Integer id, #RequestBody Car source) {
if(repository.existsById(id)) {
repository.findById(id).ifPresent(car -> car.update(source));
return ResponseEntity.noContent().build();
}else {
Car created = repository.save(source);
return ResponseEntity.created(URI.create("/" + created.getId())).body(created);
}
}
}
Possible differences from your source code could be as follow:
I used IDENTITY generator to generate the PRIMARY KEY, instead of the one you have on your entity as it was easy for me to test.
I provided ObjectMapper bean to serialize/deserialize the request body to Car object to support Java 8 LocalDateTime conversion, you may have your way to send datetime values, so that it converts to Car Object.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
// And Object mapper bean
#Bean
public static ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}
However, these differences should not matter.
application.properties
To print query logs to verify if queries are fired or not
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.username=test
spring.datasource.password=test
spring.datasource.jpa.show-sql=true
spring.jpa.open-in-view=false
logging.level.org.hibernate.SQL=DEBUG
The fact that you are updating the car object doesn't mean it updates the value in the DB. You always need to call repository.save() method to persist your changes in the DB.

How to send only the ID the of main nested objects in the body request in spring boot

I'm creating eCommerce for merchants using spring boot with JPA.
I have an issue while creating the order service.
I want to only pass the ID of the nested objects in the request body instead of sending the full nest objects because the size will be extremely big.
Here is my code.
Merchant can do many orders
Order
#Entity
#Table(name = "Orders")
#XmlRootElement
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Order extends BasicModelWithIDInt {
#Basic(optional = false)
#Column(name = "Quantity")
private Integer quantity;
#Basic(optional = false)
#Size(min = 1, max = 150)
#Column(name = "Notes")
private String notes;
#JoinColumn(name = "ProductID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JsonIgnoreProperties
private Product productID;
#JoinColumn(name = "MerchantID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Merchent merchent;
#JoinColumn(name = "OrderSatusID", referencedColumnName = "ID")
#ManyToOne(optional = false, fetch = FetchType.EAGER)
private OrderStatus orderStatus;
// Getters and Setters
}
Order Holder
public class OrderHolder {
#NotNull
private Order order;
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
OrderRepo
public interface OrderRepo extends JpaRepository<Order, Integer> {
}
Order Controller
#RestController
#RequestMapping(value = "order", produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderRestController extends BasicController<OrderHolder>{
#Autowired
private OrderRepo orderRepo;
#PostMapping("create")
public ResponseEntity<?> create(#RequestBody #Valid OrderHolder orderHolder, Principal principal) throws GeneralException {
log.debug( "create order {} requested", orderHolder.toString());
Order order = new Order();
order = orderHolder.getOrder();
System.out.println("###############"+order);
try {
order = orderRepo.save(order);
log.info( "Order {} has been created", order );
} catch (Exception e) {
log.error( "Error creating Order: ", e );
e.printStackTrace();
throw new GeneralException( Errors.ORDER_CREATION_FAILURE, e.toString() );
}
return ResponseEntity.ok( order );
}
}
I need request body to look like the below instead of including the full Merchant and Product objects inside the request.
You can make use of JsonView to return only id of product and merchant
public class OrderView {}
...
public class Product{
#Id
#JsonView(OrderView.class)
private Integer id
private String otherFieldWithoutJsonView
...
}
and then in your controller
#PostMapping("create")
#JsonView(OrderView.class) // this will return the product object with one field (id)
public ResponseEntity<?> create(#RequestBody #Valid OrderHolder orderHolder, Principal principal) throws GeneralException {
...
}
hope this can help you
Just have a separate contract class.
public class OrderContract {
private int merchantID;
private String notes;
....
//getter, setters
}
public class OrderHolder {
#NotNull
private OrderContract orderContract;
public OrderContract getOrderContract() {
return orderContract;
}
public void setOrder(OrderContract orderContract) {
this.orderContract = orderContract;
}
}
And before making a call to the Repository , translate from OrderContract to Order.
I would like to share something regarding this.
I have searched a lot on internet and tried lot of things, but the solution given here suited well for this scenario.
https://www.baeldung.com/jackson-deserialization
You need to create a Custom-deserializer for your model by extending StdDeserializer from com.fasterxml.jackson.databind.deser.std.StdDeserializer, where you just want to pass id's and not the whole object in the request.
I have given below example for User Model with Address object.
User(long userId, String name, Address addressId)
Address(long addressId, String wholeAddress)
Writing Deserializer for User class
public class UserDeserializer extends StdDeserializer<User> {
public User() {
this(null);
}
public User Deserializer(Class<?> vc) {
super(vc);
}
#Override
public User deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = p.getCodec().readTree(p);
long id = 0;
long addressId = (Long) ((IntNode) node.get("addressId")).numberValue().longValue();
return new User(id, name, new Address(addressId, null)
}
Now you have to use
#JsonDeserialize(using = UserDeserializer.class)
public Class User {
...
}
POST request
Before custom deserialization
{
"name" : "Ravi",
"addressId" : { "id" : 1}
}
After custom Deserialization
{
"name" : "Ravi",
"addressId" : 1
}
Also while GET /user/:id call you will get the whole obj like
{
"name" : "Ravi",
"addressId" : { "id" : 1, "wholeAddress" : "Some address"}
}

How to make a Restful API to handle many-to-many relation in Spring Boot?

In my Spring boot project, I have two tables names- doctors and patients. In these tables, I have following attributes-
Now, the thing is I want to create a many to many relation between these two tables for appointment as one doctor can have many patients and one patient can have appointment of multiple doctors. So, to handle this problem, I have created another table named appointment which will have doctorId and patientId as foreign key.
I need to create appointment by using JSON request body like below-
So, for this purpose I have created a model class like below-
Appointment.java
#Entity
#Table(name = "appointments")
public class Appointment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private Long appointedDoctorId;
#NotNull
private Long appointedPatientId;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "doctorId", nullable = false)
private Doctor doctor;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "patientId", nullable = false)
private Patient patient;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getAppointedDoctorId() {
return appointedDoctorId;
}
public void setAppointedDoctorId(Long appointedDoctorId) {
this.appointedDoctorId = appointedDoctorId;
}
public Long getAppointedPatientId() {
return appointedPatientId;
}
public void setAppointedPatientId(Long appointedPatientId) {
this.appointedPatientId = appointedPatientId;
}
public Doctor getDoctor() {
return doctor;
}
public void setDoctor(Doctor doctor) {
this.doctor = doctor;
}
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
Here's the Repository class-
AppointmentRepository .java
#Repository
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
Page<Appointment> findByDoctorId(Long id, Pageable pageable);
}
Here's my Service interface-
AppointmentService .java
public interface AppointmentService {
public Page<Appointment> getAllAppointmentByDoctorId(#PathVariable(value = "doctorId") Long id, Pageable pageable);
public Appointment createAppointment(#Valid #RequestBody Appointment createAppointmentRequest);
}
Here's the implementation of my Service class-
AppointmentServiceImpl.java
#Service
public class AppointmentServiceImpl implements AppointmentService {
#Autowired
AppointmentRepository appointmentRepository;
#Override
public Page<Appointment> getAllAppointmentByDoctorId(Long id, Pageable pageable) {
return appointmentRepository.findByDoctorId(id, pageable);
}
#Override
public Appointment createAppointment(#Valid Appointment createAppointmentRequest) {
return appointmentRepository.save(createAppointmentRequest);
}
}
And at last I have this Controller class-
Appointment.java
#RestController
#RequestMapping("/api")
public class AppointmentController {
#Autowired
AppointmentService appointmentService;
#GetMapping("/doctors/{doctorId}/appointments")
public Page<Appointment> getAllAppointmentsByDoctorId(#PathVariable(value = "id") Long id, Pageable pageable){
return appointmentService.getAllAppointmentByDoctorId(id, pageable);
}
#PostMapping("/insert/new/appointments")
public Appointment createAppointment(#Valid Appointment createAppointmentRequest) {
return appointmentService.createAppointment(createAppointmentRequest);
}
}
But whenever I run the project and do POST request for creating the Appointment using the mentioned request body, it is showing the following response-
So, I need to know what's the problem in my code and how can I create an appointment POST request by giving the doctorId and patientId like the way I mentioned in JSON RequestBody request.

Update multiple users by passing list of User ids

I am trying to update some user information by passing List of User-Ids as parameter
i want to update isActive field of User fo which i am passing the user ids.
Below is my controller
#PutMapping
#ResponseStatus(HttpStatus.OK)
#RequestMapping("/UserUpdate")
public ResponseEntity<?> updateUsers(List<Long> userIds) {
**userService.updateUsers(userIds);**
return ResponseEntity.ok(200);
}
updateUsers() is a method in my Service where i have to write the logic
I tried something like below but it's not working
public void updateUsers(List<Long> userIds) {
List<Users> userList= userRepository.findAll();
for (Long i : userIds) {
for ( Users user : userList)
{
if(userRepository.findById(i) != null)
{
user.setIsActive(9L);
user.setUserName("Update Test");
}
my dto
public class UserDto {
private List<Users> userList;
private String appName="Users Project";
// getters and setters removed for brevity
And my Users entity class
#Entity
#Table(name="USERS")
public class Users {
#Id
#Column(name="USER_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#Column(name="NAME")
private String userName;
#Column(name="ACTIVE")
private Long isActive;
// getters and setters removed for brevity
Alternatively you can use the following code
#Modifying
#Query("update Users u set u.isActive = ?1, u.userName = ?2 where u.userId in ?3")
void updateUsers(Long isActive, String userName, List<Long> userId);
Add this code in your userRepository and use the method.
public void updateUsers(List<Long> userIds) {
for (Long i : userIds) {
User user = userRepository.findById(i);
if(user != null){
user.setIsActive(9L);
user.setUserName("Update Test");
// call your update method here (this is not stated in your code)
}
}
}

Hibernate doing a delete query when I try to update an object in Spring

I have the following class:
#Entity
#Table(name="tbl_books")
public class Book{
private int id_book;
private String isbn;
private Set<Author> authors;
#ManyToMany(cascade=CascadeType.ALL, fetch= FetchType.EAGER)
#JoinTable(name="tbl_books_tbl_authors",
joinColumns={#JoinColumn(name="id_book")},
inverseJoinColumns= {#JoinColumn(name="id_author")})
public Set<Author> getAuthors() {
return authors;
}
public void setAuthors(Set<Author> authors) {
this.authors = authors;
}
This is the function in BookController for updating the book info:
#RequestMapping(method=RequestMethod.POST)
public String dataBook(#Valid BookBean bb, BindingResult result){
if (result.hasErrors()){
return "redirect:/books/formBook";
}
booksService.saveBook(bb);
return "redirect:/books";
}
In booksService I have this one:
#Override
#Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void saveBook(BookBean lb) {
DataParser dp = new DataParser();
booksDAO.sBook(dp.fromBookBeanToBook(lb));
}
In booksDAO:
#Override
public void sBook(Book book) {
sessionFactory.getCurrentSession().saveOrUpdate(book);
}
Then, when I try to update a Book from that form Hibertate does this:
Hibernate: update tbl_books set date=?, isbn=? where id_book=?
Hibernate: delete from tbl_books_tbl_authors where id_book=?
Why does Hibernate do this and how can I fix it?
Ok, got it, when trying to update the Authors Set was empty, so Hibernate did a delete instead of update.

Categories