Spring data jpa native query. Insert object - sql

I'm using a native query on my repository. I want to insert object data, but the query doesn't understand the object fields. How to solve this problem, I don't want to have a method with a lot of parameters.
My class:
public class StudentDTO {
private long numberzachetka;
private String fio;
private Date entrydate;
private int course;
private int numbergroup;
private long specialty;
private long faculty;
//getters, setters..}
Native query:
#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
Student entity:
#Entity
#Table(name="student")
public class Student {
#Id
#Column(name="numberzachetka", nullable = false)
private long numberzachetka;
#Column(name="fiostudent", nullable = false, length = 100)
private String fio;
#Temporal(TemporalType.DATE)
#Column(name = "entrydate", nullable = false)
private Date entrydate;
#Column(name="course", nullable = false)
private int course;
#Column(name="numbergroup", nullable = false)
private int numbergroup;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "specialtykey", nullable = false)
private Specialty specialty;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "facultynumber", nullable = false)
private Faculty faculty;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "student")
private Set<Performance> performances;

#Query(value ="insert into student(numberzachetka,fiostudent,entrydate,course,numbergroup,specialtykey,facultynumber)" +
" values" +
" (:student.numberzachetka,:student.fio,:student.entrydate,:student.course,:student.numbergroup,:student.specialty,:student.faculty)", nativeQuery = true)
void addNewStudent(#Param("student") StudentDTO studentDTO);
This is not the right way to save value in db using JPA repository.
If you want to save value in DB there is very simpler way, follow below steps:
Copy all the values from the studentDTO to Student object using below code.
Student student = new Student();
BeanUtils.copyProperties(studentDto,student);
create StudentRepository interface
#Repository public interface StudentRepository extends
JpaRepository<Student, Long> { }
#Autowired above Repository in the service class
#Autowired private StudentRepository studentRepository;
Save the Student object using repository predefined save method.
studentRepository.save(student);
These are the minimal change you want to save object in db, instead of going through native SQL query.

Within the entity you could have a constructor accepting the DTO. The constructor transfers the DTO attributes to the fields of the entity. Then you can just use the JpaRepository save method. You don't need the select statement. Keep in mind to add also an empty constructor which is required by JPA.
Another Idea which I use in my projects is to use mapstruct to transfert attributes from DTOs to entities.
The way you present your idea is not possible. JPA does not know how to map a DTO to an entity.

Related

How to access the join column (Foreign key) in Spring Data JPA without loading the mapped entity

I have a POST entiry and HeadingSlugEntity.
public class PostEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String postId;
private String uid;
private String dateCreated;
private String dateUpdated;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "headingSlugId", referencedColumnName = "headingSlugId")
private HeadingSlugEntity headingSlugEntity;
}
I have a 1:1 mapping for HeadingSlugEntity. When I save post entity and headingSlugEntity together, headingSlugId is getting auto-populated. That is working fine.
The next time when I fetch the post entity, I need to access the headingSlugId without getting the headingSlugEntity. (At this point of time, I don't need HeadingSlugEntity, I just need its ID.) I just need to get the headingSlugId. How to do it.
Since this entity is not having the field headingSlugId, I can't set and get it. If I add a field named headingSlugId inside post entity, I will get hibernate duplicate field error. If I put
insertable = false, updatable = false)
and add field headingSlugId, the headingSlugId will be Null.
What basic stuff I am missing here ?
You can map the foreign key to the entity twice in this case, one for actual ORM and another for getting the FK without actually firing a new query.
public class Answer {
#Column(name = "headingSlugId", insertable = false, updatable = false)
private Integer headingSlugId;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "headingSlugId", referencedColumnName = "headingSlugId")
private HeadingSlugEntity headingSlugEntity;
}

How to include additional data in one entity from other tables (via joins or subqueries) and still have it be insertable/updatable?

So I have the following code working correctly on my ecommerce site.
#Entity
#Table(name = "v_customer_wishlist")
#NamedQuery(name = "VCustomerWishlist.findAll", query = "SELECT w FROM VCustomerWishlist w")
public class VCustomerWishlist implements Serializable {
#Id
#Column(name = "cart_id")
private int _cartId;
//get/set methods
...
#OneToMany(mappedBy = "_wishlist", cascade = CascadeType.ALL)
private List<VCustomerWishlistItem> _items;
//get/set methods
}
#Entity
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(int cartId, int productId) {
VCustomerWishlistItemPK id = new VCustomerWishlistItemPK (cartId, productId);
setId(id);
}
#EmbeddedId
private VCustomerWishlistItemPK id; //is PK for cartId and productId
//get/set methods
...
#ManyToOne
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#Column(name = "product_name")
private String _productName;
//get/set methods
...
}
Then somewhere in my backing bean, I could do somethin like this (simplified version):
...
VCustomerWishlist wishlist = getCustomer().getWishlistById(cartId);
...
VCustomerWishlistItem item = new VCustomerWishlistItem(wishlist.getId(), product.getId());
...
item.setSequenceNum(wishlist.getItems().size()+1);
item.setProductName(product.getName());
item.setQuantity(1);
wishlist.addItem(item);
wishlistItemService.save(item);
...
So I can add items (product references) to wishlist and JPA will correctly generate the INSERT INTO queries and so forth.
However, upon thinking about it, I thought it would be better to retrieve this data directly from my 'master_products' table instead of what was stored in the VCustomerWishlistItem.
This way I would always have the most up-to-date productName, unitPrice and so forth for wishlist items saved weeks or months before.
The thing is, if I modify the view in my database to include this additional info by adding joins or subqueries; as soon as add joins or subqueries to my view, it becomes non-inserable/updatable.
I thought that it could be done via JPLQ in one #NamedQuery definition, but I understand these are designed to be used manually when retrieving desired sets. As opposed to the nice built-in way that JPA automatically retrieves the wishlist.items resolving it with the indicating annotation properties.
The fantasy property would be one where I can specify a direct table source for the entity, ignoring the join and subquery tables.
So for example, if the source for 'v_customer_wishlist_items_readonly' was:
SELECT
`cwi`.`cart_id` AS `cart_id`,
`cwi`.`product_id` AS `product_id`,
`cwi`.`sequence_num` AS `sequence_num`,
`mp_readonly`.`product_name` AS `product_name`,
`mp_readonly`.`product_web_id` AS `product_web_id`,
`mp_readonly`.`unit_price` AS `unit_price`,
`cwi`.`quantity` AS `quantity`,
`mp_readonly`.`unit_price`*`csci`.`quantity` AS `item_subtotal`,
`cwi`.`create_datetime` AS `create_datetime`,
`cwi`.`update_datetime` AS `update_datetime`
FROM
`customer_wishlist_items` `cwi` JOIN `master_products` `mp_readonly` ON `cwi`.`product_id` = `mp_readonly`.`product_id`
ORDER BY `cwi`.`sequence_num`;
It would be ideal to have a an annotation where I could indicate that primary table name is 'customer_wishlist_items', so all updates/inserts would only apply to this table and changes to the rest of the read-only fields would be ignored.
So somethint like this:
#Entity
#Table(name = "v_customer_wishlist_items_readonly")
#PrimaryTable(name = "customer_wishlist_items") //fantasy annotation
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT s FROM VCustomerWishlistItem s")
public class VCustomerWishlistItem implements Serializable {
...
Does anyone know what would be the correct way of implementing this?
Thanks a lot in advance!
Why not use derived ids or the MapsId to let JPA set your foreign key/id columns for you?
#Entity
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(VCustomerWishlist cart, Product product) {
this._wishList = cart;
this._product = product;
setId(new VCustomerWishlistItemPK());//JPA will populate this for you
}
#EmbeddedId
private VCustomerWishlistItemPK id; //is PK for cartId and productId
//get/set methods
...
#ManyToOne
#MapsId("cartId")
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#MapsId("productId")
#JoinColumn(name = "product_id")
private Product _product;
//get/set methods
...
}
With this, you don't need to have or lookup the cartId/productId values at all as JPA will figure them out and set them for you, allowing you do just use code like:
VCustomerWishlistItem item = new VCustomerWishlistItem(wishlist, product);
...
item.setSequenceNum(wishlist.getItems().size()+1);
item.setQuantity(1);
wishlist.addItem(item);
wishlistItemService.save(item);
You should probably just set the sequenceNum and add the item to the wishlist in the item constructor, though I'm not a fan this approach to sequencing as it can lead to concurrency issues and problems maintaining it.
You can also do away with the EmbeddedId if you don't need it within your entity and use it as a primary key class; you'd just have to change the property names within it to match the relationships names from the entity:
#Entity
#IdClass(VCustomerWishlistItemPK.class)
#Table(name = "v_customer_wishlist_items")
#NamedQuery(name = "VCustomerWishlistItem.findAll", query = "SELECT i FROM VCustomerWishlistItem i")
public class VCustomerWishlistItem implements Serializable {
...
public VCustomerWishlistItem(VCustomerWishlist cart, Product product) {
this._wishList = cart;
this._product = product;
}
...
#ManyToOne
#Id
#JoinColumn(name = "cart_id")
private VCustomerWishlist _wishlist;
//get/set methods
...
#Id
#JoinColumn(name = "product_id")
private Product _product;
//get/set methods
...
}
The primary key class might then look like:
public class VCustomerWishlistItemPK {
public Integer _product;
public Integer _wishlist;
//optional getter/setter methods..
}
The properties within the ID class must match the names of the properties in your entities, but use the type of the primary key from the referenced class.

Cant get hibernate to update object without EntityExistException being thrown

I am trying to do a bidirectional one to one relation and when updating the AccountExtrasModel on the first save it works fine but when updating I get either an error or the sql statements adds an insert and then a delete instead of an update.
import lombok.*;
import javax.persistence.*;
#ToString
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "Account")
public class AccountModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Account_ID;
#Column(nullable = false, updatable = false)
private String name;
#Column(nullable = false, unique = true, updatable = false)
private String email;
#Column(nullable = false, updatable = false)
private String password;
#OneToOne(mappedBy = "accountModel", cascade = CascadeType.ALL, orphanRemoval = true)
private AccountExtrasModel accountExtras;
public AccountModel addExtras(AccountExtrasModel accountExtrasModel) {
accountExtrasModel.setAccountModel(this);
this.setAccountExtras(accountExtrasModel);
return this;
}
}
import lombok.*;
import javax.persistence.*;
#Setter
#Getter
#NoArgsConstructor
#ToString
#Entity
#Table(name = "AccountExtras")
public class AccountExtrasModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long ID;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private AccountModel accountModel;
#Lob
private String description;
private String[] myVideos;
private String[] likedVideos;
private String imageReference;
}
If i change the #MapsId to #JoinColumn in AccountExtrasModel then i get the desired result but what its doing is inserting a new row and linking it to acccount and then deleting the old row instead of doing an update.
This is the error im getting:
{
"timestamp": "2018-04-26T18:19:01.657+0000",
"status": 500,
"error": "Internal Server Error",
"message": "A different object with the same identifier value was already associated with the session : [com.alttube.account.models.AccountExtrasModel#1]; nested exception is javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.alttube.account.models.AccountExtrasModel#1]",
"path": "/update_account"
}
What can i do to get the desired result which is to simply perform an update on the accountExtrasModel to the corresponding account to which it belongs?
If you update an AccountModel instance by setting a new instance of AccountExtrasModel, as you do in addExtras(), then it is normal to have a delete+insert. By updating the id you would end with orphaned records.
When you set new values on AccountExtrasModel instance, check if accountExtras is initialized. If not, do the addExtras() stuff. If it is, do not replace it, just change it, so hibernate will generate an update on the extras table (but keep the record id unchanged).
Instead of use the method save try use the method merge, because in case of object exist in database the framework will update this object.
Case the object don't exist, the framework will insert as normal.
Regards!

JPQL query for left outer join over parent vs inherited object

Entities/Model:
#Inheritance(strategy=InheritanceType.JOINED)
public class UserAccount implements CommonUserAccount {
#Id
private Long id;
private String email;
#Embedded
private PersonalInfo personalInfo = new PersonalInfo(); // name/surname - regular stuff
#ElementCollection
#CollectionTable(name = "UserAccountTags", joinColumns = #JoinColumn(name = "accountId", nullable = false))
#Column(name = "tag")
//#Transient
private Set<String> tags = new HashSet<String>();
#ElementCollection
#CollectionTable(name = "UserAccountRoles", joinColumns = #JoinColumn(name = "accountId", nullable = false))
#Enumerated(EnumType.STRING)
#Column(name = "userRole")
private Set<UserAccountRole> userRoles = new HashSet<UserAccountRole>();
// regular getters/setters
}
#Entity
#Table
#PrimaryKeyJoinColumn(name = "useraccountid")
public class DemoUserAccount extends UserAccount implements CommonUserAccount {
#Column
private String passwordHash;
#Column
private Long failedLogins;
#Column
#Temporal(TemporalType.TIMESTAMP)
Date lockedAt;
// regular getters/setters
}
Question:
Is it possible to build query using JPQL (for JPA2.0) that would return DemoUserAccounts joined on parent table - UserAccounts? Doing this would assume I can filter on tags/user_roles as well. In general some records will not have DemoUserAccount specific fields filled in.
When you do a SELECT from DemoUserAccount, you already have the UserAccount fields available to do a query using them.
So, if you want to filter by email and failedLogins:
SELECT d FROM DemoUserAccount d WHERE d.email = 'you#you.com' AND d.failedLogins > 3

JPA #ElementCollection generates strange unique key

I have an entity class PositionOrdering which contains an element collection:
#ElementCollection(targetClass = Position.class, fetch = FetchType.EAGER)
#CollectionTable(name = "POSITION_ORDERING_POSITION",
joinColumns = #JoinColumn(name = "position_ordering_id"))
#OrderColumn
List<Position> positions = new ArrayList<>();
When hibernate generates the database structure, it looks like this:
CREATE TABLE wls.position_ordering_position
(
position_ordering_id bigint NOT NULL,
positions_id bigint NOT NULL,
positions_order integer NOT NULL,
...
}
It's ok and exactly what I was expected. But it also generate a unique contsraint on positions_id column. It is strange, because the position id should be unique only per ordering, so any of the following unique keys would be ok:
position_ordering_id + positions_order
position_ordering_id + positions_id
But not on the single column of positions_id.
Because the constraint is generated automatically, I can't ignore or remove it simply.
Can I configure my collection to create correct unique constraint or at least not to create any?
UPDATE:
As for request, here is the skeleton of the Position entity:
#Entity
#SequenceGenerator(name = EntityBase.SEQUENCE_NAME,
sequenceName = "POSITION_ID_SEQ")
#Table(name = "position")
public class Position extends EntityBase {
// Lots of fields, like row, column number, and type, etc.
}
Where EntityBase is a simple class with some utility function and with Id:
#MappedSuperclass
public abstract class EntityBase implements Serializable, Cloneable {
public static final String SEQUENCE_NAME = "SEQUENCE_GENERATOR";
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = SEQUENCE_NAME)
protected Long id;
//..
}
#ElementCollection is used for mapping basic types or #Embedded classes, not entities. From the documentation
An ElementCollection can be used to define a one-to-many relationship to an Embeddable object, or a Basic value (such as a collection of Strings).
Since Position is an #Entity, you should map it as #OneToMany or #ManyToMany. I don't know the exact reason why are you getting that unique key generated, but I guess you can expect unpredictable results if you use the annottion in a was that it was not intended for.
As Predrag Maric described it in the accepted answer, the problem was that Position was not an `Embeddable'. My solution was:
I created a support class which wraps the Position into an #Embeddable entity:
#Embeddable
//#Table(name = "position_ordering_position")
public class PositionOrderingPosition {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "position_id", nullable = false)
private Position position;
public PositionOrderingPosition() {
}
public PositionOrderingPosition(Position position) {
this.position = position;
}
public Position getPosition() {
return position;
}
}
Also I changed the Element collection to this:
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "POSITION_ORDERING_POSITION",
joinColumns = #JoinColumn(name = "position_ordering_id"))
#OrderColumn
List<PositionOrderingPosition> positions = new ArrayList<>();
Now it creates the same table, but with the right constraints.