Having an issue using Hibernate/JPA custom queries - java

first time poster here. I am having an issue using a custom finder query with spring/jpa.
I have a simple ecommerce project using ORM with 4 tables in database. Customer accounts, list of products, current cart, and transaction history.
The problem I am having is using the specific finder methods.
In order to ensure the value of each Order No be incrementally inserted upon checkout(save and flush a list of all items in current cart to transaction history) I have to find that specific value by account Id, and ++ for every checkout, before setting it to save and Flush.
Can someone point out what I am doing wrong?
cart Dao with finder methods:
public void deleteAllByAccountId(int accountId);
public void deleteByBookIdAndAccountId(int bookId, int accountId);
public List<CartEntity> findAllByAccountId(int accountId);
#Query ("SELECT MAX(orderNo) FROM current_cart WHERE accountid = :#{customer_accounts.accountid}")
public CartEntity findByAccountId(#Param("accountid") int accountId);
cart Entity:
#Table(name = "current_cart")
public class CartEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) // Id is the primary auto generated key
#Column(name ="orderno")
private int orderNo;
#Column(name = "accountid")
private int accountId;
#Column(name = "cost")
private int bookCost;
#Column(name = "quantity")
private int quantity;
#Column(name = "booktitle")
private String bookTitle;
#Column(name = "bookid")
private int bookId;
#Table(name = "customer_accounts")
public class AccountEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) // Id is the primary auto generated key
#Column(name = "accountid")
private int accountId;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
#Column(name = "firstname")
private String firstname;
#Column(name = "lastname")
private String lastname;
checkout logic:
public void Checkout(int accountId) {
List<CartEntity> FetchedCartEntities = cartDao.findAllByAccountId(accountId);
List<TransactionHistoryEntity> transactionsToCopy = new ArrayList<TransactionHistoryEntity>();
for (CartEntity cartEntity : FetchedCartEntities) {
TransactionHistoryEntity transaction = new TransactionHistoryEntity();
BeanUtils.copyProperties(cartEntity, transaction);
transactionsToCopy.add(transaction);
}
transactionHistoryDao.saveAllAndFlush(transactionsToCopy);
cartDao.deleteAllByAccountId(accountId);
}
Thank you guys

Related

Spring Boot Data JPA detached entity passed to persist on #OneToMany and #OneToOne

Problem
I am trying to store an object in my Postgres database. This consists of the Order.class, (List) OrderDevice.class, and a Department.class.
The important thing is that the OrderDevices are always stored new in the DB, but a Department may already exist.
When I try to save the object to my database using save I get the following error message: (shown below)
I get the error message "detached entity passed to persist: com.niclas.model.OrderDevice" if the department does not exist yet, if the department exists the error message looks like this: "detached entity passed to persist: com.niclas.model.Department".
Solution attempts
This solution cannot be used because I do not use bidirectional mapping.
(I don't want to use a bidirectional mapping because I want to access the departments without an order.)
I also tried to change the Cascade types to MERGE like in this solution
I also tried using #Transactional on the method
I also tried to save the children in the database first and then the parent like this:
departmentRepository.save(order.getDepartment()); orderDeviceRepository.saveAll(order.getDevices()); orderRepository.save(order);
I hope I have described my good enough and I am happy about suggestions for solutions
Error.log
The log can be viewed here. (The formatting did not work here)
Order.class
#Entity
#Table(name = "orders")
public class Order extends AuditModel {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "order_id")
private String orderId;
#Column(name = "department_id")
private long departmentId;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "department", referencedColumnName = "id")
private Department department;
#JsonProperty("deviceList")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", referencedColumnName = "order_id")
private List<OrderDevice> devices;
#JsonProperty("forename")
#Column(name = "sender_forename")
private String senderForename;
#JsonProperty("surname")
#Column(name = "sender_surname")
private String senderSurname;
#Column(name = "notes", columnDefinition = "TEXT")
private String notes;
#Column(name = "month")
private int month;
#Column(name = "year")
private int year;
public Order() {
}
... Getter/Setters
}
OrderDevice.class
#Entity
#Table(name = "order_devices")
public class OrderDevice extends AuditModel{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column( name = "order_id", insertable = false, updatable = false )
private String orderId;
#Column(name = "device_id")
private long deviceId;
#Column(name = "device_name")
private String deviceName;
#Column(name = "priceName")
private String priceName;
#Column(name = "price")
private double price;
#Column(name = "count")
private int count;
public OrderDevice() {
}
... Getters/Setters
}
Department.class
#Entity
#Table(name = "departments")
public class Department {
//TODO add Form Validation
//TODO better Naming for From Attributes on Frontend and Backend
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO) //TODO better config for GenerationType
private long id;
#Column(name = "department_name")
private String department;
#Column(name = "contact_person_forename")
private String forename;
#Column(name = "contact_person_surname")
private String surname;
#Column(name = "contact_person_mail")
private String mail;
#Column(name = "street")
private String street;
#Column(name = "house_number")
private String houseNumber;
#Column(name = "location")
private String location;
#Column(name = "postal_code")
private int postalCode;
#Column(name = "country")
private String country;
#Column(name = "auto_send_invoice")
private boolean autoSend;
#Column(name = "registered")
private boolean registered;
public Department() {
}
... Getter/Setters
}
OrderController.class
#Slf4j
#RestController
public class OrderController {
private final DepartmentRepository departmentRepository;
private final OrderRepository orderRepository;
private final OrderDeviceRepository orderDeviceRepository;
public OrderController(OrderRepository orderRepository, DepartmentRepository departmentRepository,
OrderDeviceRepository orderDeviceRepository) {
this.orderRepository = orderRepository;
this.departmentRepository = departmentRepository;
this.orderDeviceRepository = orderDeviceRepository;
}
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody Order order) throws JsonProcessingException {
order.setOrderId(order.generateOrderId());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
order.getDevices().forEach(orderDevice -> {
orderDevice.setOrderId(order.getOrderId());
});
//departmentRepository.save(order.getDepartment());
//orderDeviceRepository.saveAll(order.getDevices());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
Update
If the objects are created in this way, no error will occur and the order will be successfully saved in the database.
However, I don't understand why it works this way and not via ObjectMapper. Does anyone know why?
#PostMapping("/orders/add")
public ResponseEntity<Order> addDepartment(#RequestBody JsonNode jsonNode) throws JsonProcessingException {
Order order = new Order();
JsonNode departmentJson = jsonNode.get("department");
Department department;
if ( departmentJson.get("id").canConvertToInt() ) {
department = departmentRepository.findDepartmentById(departmentJson.get("id").asInt());
} else {
department = new Department();
department.setDepartment(departmentJson.get("department").asText());
department.setForename(departmentJson.get("forename").asText());
department.setSurname(departmentJson.get("surname").asText());
department.setMail(departmentJson.get("mail").asText());
department.setStreet(departmentJson.get("street").asText());
department.setHouseNumber(departmentJson.get("houseNumber").asText());
department.setLocation(departmentJson.get("location").asText());
department.setPostalCode(departmentJson.get("postalCode").asInt());
department.setCountry(departmentJson.get("country").asText());
department.setAutoSend(departmentJson.get("autoSend").asBoolean());
department.setRegistered(departmentJson.get("registered").asBoolean());
}
order.setDepartment(department);
order.setOrderId(order.generateOrderId());
order.setDepartmentId(department.getId());
List<OrderDevice> orderDevices = new ArrayList<>();
JsonNode devices = jsonNode.get("deviceList");
for (JsonNode node : devices) {
//TODO replace this mess with objectMapper
if (node.has("count") && node.get("count").asInt() != 0){
OrderDevice device = new OrderDevice();
device.setOrderId(order.getOrderId());
device.setDeviceId(node.get("id").asLong());
device.setDeviceName(node.get("deviceName").asText());
device.setPriceName(node.get("priceName").asText());
device.setPrice(node.get("price").asDouble());
device.setCount(node.get("count").asInt());
orderDevices.add(device);
}
}
order.setDevices(orderDevices);
order.setSenderForename(jsonNode.get("forename").asText());
order.setSenderSurname(jsonNode.get("surname").asText());
order.setNotes(jsonNode.get("notes").asText());
DateTime dateTime = new DateTime();
order.setMonth(dateTime.getMonthOfYear());
order.setYear(dateTime.getYear());
orderRepository.save(order);
return new ResponseEntity<>(order, HttpStatus.CREATED);
}
You can try to use instead of orderRepository.save(order) use orderRespostiory.saveOrUpdate(order).

trying to save a HashMap<Item, Integer> in h2 database failed because of "Value too long ... BINARY(255)"

I am using Spring Boot with h2 db and JPA Repository.
I am adding an Item entity to a HashMap<Item, Integer>, Integer for quantity, and it works (seen it in debugger), but when it runs:
orderRepository.save(orderMapper.toEntity(orderDto));
it fails to add the HashMap to the database. I want an order entry with a list of items from which I will iterate using angular and display them to the user.
This is the error I am getting:
org.h2.jdbc.JdbcSQLDataException: Value too long for column """ITEMS_LIST"" BINARY(255)": "X'aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c647870... (384)"; SQL statement:
insert into orders (created_date, items_list, total_cost, id) values (?, ?, ?, ?) [22001-199]
This is the Order entity:
#Entity
#Table(name = "orders")
public class Orders {
#Id
#GeneratedValue
#Column(name = "id")
private Long id_order;
#Column(name = "created_date")
#Temporal(TemporalType.DATE)
private Date createdDate;
#Column(name = "total_cost")
private double totalCost;
#OneToOne(mappedBy = "order")
private User user;
#Column(name = "items_list")
private HashMap<Item, Integer> item = new HashMap<>();
Item Entity :
#Entity
#Table(name = "item")
public class Item implements Serializable {
#Id
#GeneratedValue
#Column(name = "id")
private Long id;
#Column(name = "price")
private double price;
#Column(name = "name")
private String name;
#Column(name = "category")
private String category;
#Column(name = "quantity")
private int quantity;
User Entity:
#Entity
public class User {
#Id
#GeneratedValue
#Column(name = "user_id")
private Long id;
#Column(name = "username")
private String username;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "order_id", referencedColumnName = "id")
private Orders order;
I think the problem is that you cannot store HashMap objects in db, but if it is possible could you tell me how or else how can I store a (key, value) list in the database?
Or maybe instead of storing the whole Item entity to the db, should I only store the item Id and quantity?

Using not hard coded values in query, jpa repository and spring boot

I'm using a query in my spring boot project with hard coded values and that is fine:
#Query("select user from Users user where user.mobileNumber=?1 and not user.status=-2")
Users FindNotDeletedUserByMobileNumber(String MobileNumber);
But, I wanted to use not hardcoded values, eg. reading from an enum, I tried this :
#Query("select user from Users user where user.mobileNumber=?1 and not user.status=com.taxikar.enums.User_Status.Deleted")
Users FindNotDeletedUserByMobileNumber(String MobileNumber)
But this one gives error while building :
'userRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract com.taxikar.entity.Users com.taxikar.repository.UserRepository.FindNotDeletedUserByMobileNumber(java.lang.String)!
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588) ~[spring-beans-4.3.14.RELEASE.jar:4.3.14.RELEASE]
I use this enum values in my other classes and they are working fine, for example:
if (user.getStatus() == User_Status.Deleted.getId())
return new BaseResponse(BR_Status.error.getId(), BR_ErrorCodes.NotAuthorizedUser.getStringValue() + "01",
"error 755", user.getId());
Even using .getId or .getStringValue like the one above but at end of my query doesn't solve anything. What am I doing wrong ?
Here is my enums code :
public enum User_Status implements IResponse
{
Deleted(-2),
Unauthorized(-1),
NotCompleteGeneralInfo(0),
CompleteGeneralInfo(1);
private int value;
private String stringValue;
User_Status(int value)
{
this.value = value;
}
User_Status(String stringValue){this.stringValue=stringValue;}
#Override
public int getId()
{
return value;
}
#Override
public String getStringValue()
{
return stringValue;
}
}
This enum implements IResponse which is like this :
public interface IResponse
{
String getStringValue();
int getId();
}
Here Is my repository :
public interface UserRepository extends JpaRepository<Users, String>
{
#Query("select user from Users user where user.mobileNumber=?1 and not user.status=com.taxikar.enums.User_Status.Deleted")
Users FindNotDeletedUserByMobileNumber(String MobileNumber);
}
And here is my entity class :
#Entity
#Table(name = "users")
public class Users
{
// these fields are feed by us not the user
#Id
#GeneratedValue(generator = "uuid2")
#Column(columnDefinition = "char(36)")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id;
#Column(name = "STATUS") // User status ===>-2: Deleted , -1: unauthorized , 0: user info is not complete , 1: complete user
private int status;
#Column(name = "RATE")
private String rate;
//Not Optional fields
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "SEX") // Sex ====> 1:women 2:men
private int sex;
#Column(name = "MOBILE_NUMBER")
private String mobileNumber;
#Column(name = "USER_IMG")
private String userImg;
#Column(name = "IDENTITY_NUMBER")
private String identityNumber;
#Column(name = "USER_IDENTITY_CARD_IMG")
private String userIdentityCardImg;
//Optional fields
#Column(name = "EMAIL")
private String email;
#Column(name = "BIRTHDAY")
private String birthday;
#Column(name = "DESCRIPTION")
private String description;
// not Optional fields for driver
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DRIVER_DETAIL")
private DriverDetail driverDetail;
//Login related fields
#Column(name = "TOKEN")
private String token;
#Column(name = "TOKEN_TIMESTAMP")
private Timestamp tokenTimeStamp;
#Column(name="SMS_COUNT")
private int smsCount;
#Column(name="SMS_COUNT_TIMESTAMP")
private Timestamp smsCountTimeStamp;
+++ constructor and setters and getters.
}
Try this:
#Query("select user from Users user where user.mobileNumber=?1 and user.status<>?2")
Users FindNotDeletedUserByMobileNumber(String MobileNumber, int status);
and pass in -2 as parameter when you call that repository method

Foreign Key is Null

I trying to make relation between phonebook and user through jpa, when the current logged in user creates a contact the foreign key of user in table phonebook remains null. I checked couple of question here but it did'not work for me.
Phonebook
#Entity
#Table(name = "Phonebook")
public class Phonebook {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "phonebook_id")
private Long id;
#Column(name = "phone", length = 15, nullable = false)
private String phoneNumber;
#Column(name = "firstname", length = 50, nullable = false)
private String firstName;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
//getters and setters
User
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#Column(name = "email")
private String email;
#Column(name = "password")
#Length(min = 5, message = "*Your password must have at least 5 characters")
#org.springframework.data.annotation.Transient
private String password;
#OneToMany(mappedBy = "user")
private List<Phonebook> phonebooks;
//getters and setters
PhonebookController
#RequestMapping(value = {"/home/phonebook"}, method = RequestMethod.GET)
public String showPage(Model model, #RequestParam(defaultValue = "0") int page){
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findUserByEmail(auth.getName());
model.addAttribute("data",phonebookRepository.findAllByUserId(user.getId(),PageRequest.of(page,10)));
model.addAttribute("currentPage",page);
return "/home/phonebook";
}
#PostMapping("/home/phonebook/save")
public String save (Phonebook p){
phonebookRepository.save(p);
return "redirect:/home/phonebook";
}
PhonebookRepository
#Repository("phonebookRepository")
public interface PhonebookRepository extends JpaRepository<Phonebook,Integer> {
List<Phonebook> findAllByUserId(Long id, Pageable pageable);
}
what You have to do the first create a user object and set the id and then persist the phone book.
You must persist PhoneBook together with your User.
User u = new User();
// Set properties for your User...
PhoneBook p = new PhoneBook();
// Set properties for your phonebook...
// Store phone book to user:
u.setPhoneBook(Collections.singletonList(p));
userRepository.save(p);

Trouble with EntityManager Query

For some reason I don't get results when running this from method.
#SuppressWarnings("unchecked")
public Object[] getPointRaiting(Long id) {
EntityManager em = createEntityManager();
em.getTransaction().begin();
Query allPointsQuery = em
.createQuery("Select AVG(r.RATING) from Ratings r WHERE r.POINT_ID = :point");
allPointsQuery.setParameter("point", id);
Object[] rating = (Object[]) allPointsQuery.getSingleResult();
em.getTransaction().commit();
em.close();
closeEntityManager();
return rating;
}
SQL should be correct as it executes in HSQL db manager and returns the correct value. But java function stops running at query. It does'nt throw any errors just stops. I'm out of ideas, where should I look? (Other similiar methods with count and select all work correctly).
Using HSQLDB and Hibernate.
Found that the following error was thrown:
org.hibernate.QueryException: could not resolve property: RATING of: kaart.entities.Ratings [Select AVG(r.RATING) from kaart.entities.Ratings r WHERE r.POINT_ID = :point]
But this does not solve it for me as the RATING property is defined in table and in entity...
#Entity #Table(name = "RATINGS")
public class Ratings implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Point point;
#ManyToOne
private User user;
#Column(name = "RATING")
private int rating;
private static final long serialVersionUID = 1L;
public Ratings() {
super();
}
public Ratings(Point point, User user, int rating) {
this.point = point;
this.user = user;
this.rating = rating;
}
/*all getters and setters here*/}
#Entity
#Table(name = "POINT")
public class Point implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "point")
private List<Category> pointsByCategory;
#OneToMany(mappedBy = "point")
private List<Ratings> pointRatings;
#Column(name = "NAME")
private String name;
#Column(name = "LOCATION")
private String location;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "LINK")
private String link;
#ManyToOne
private User user;
private static final long serialVersionUID = 1L;
public Point() {
super();
}
public Point(String name, String location, String description, String link, User user) {
this.name = name;
this.location = location;
this.description = description;
this.link = link;
this.user = user;
} /* getters and setters*/
You can only pass JP-QL inside em.createQuery().
But seems you are using native SQL with values like r.RATING, r.POINT_ID, which may not be in the Java entity. Replace it with equivalent java entity variable, could be pointId
em.createQuery("Select AVG(r.RATING) from Ratings r WHERE r.POINT_ID = :point");
If you want to use native sql, you can use em.createNativeQuery().
Most likely this problem is caused by caps-locked property names: RATING, POINT_ID.
Try replacing them with the ones that you use in Ratings class, probably:
Select AVG(r.rating) from Ratings r WHERE r.point.id = :point_id

Categories