I've got Product and Rating entities in JPA.
In Product.java:
#OneToOne(cascade={CascadeType.PERSIST,CascadeType.MERGE})
#JoinColumn(name="rating_id")
#JsonManagedReference("product-rating")
private Rating rating;
In Rating.java:
#OneToOne(mappedBy="rating",cascade=CascadeType.MERGE)
#JsonBackReference("product-rating")
private Product product;
In my JPAService implementation:
#Override
#Transactional
public void updateCartLineRating(CartLine cartLine, int rating) {
cartLine.setRating(rating);
em.merge(cartLine);
Product product = cartLine.getProduct();
Rating productRatingObject = product.getRating();
// Here I calculate new rating, then I set it:
productRatingObject.setRating(productRating);
// I associate the new Rating to Product (which is the relationship owner) and viceversa
productRatingObject.setProduct(product);
product.setRating(productRatingObject);
em.merge(product);
}
When I do that, my database is updated, while my views are not. I have to restart the server to make these changes appear.
I tried to write em.refresh(product); after em.merge(product); but I get this error:
Can not refresh not managed object: package.Product#33891d5d
I'm making confusion and I don't know how to solve this problem. Could you please help me?
Try using the entityManager to find the entity again. This should provide the entity with updated values.
Related
I'm currently learning Spring-Boot and Spring-Data-JPA.
I'm using a postgresql database for storing the data.
My goal is to store ingredients with a unique and custom ID (you just type it in when creating it), but when another ingredient with the same ID gets inserted, there should be some kind of error. In my understanding, this is what happens when I use the #Id annotation, hibernate also logs the correct create table statement.
This is my Ingredient class:
public class Ingredient {
#Id
#Column(name = "ingredient_id")
private String ingredient_id;
#Column(name = "name")
private String name;
#Column(name = "curr_stock")
private double curr_stock;
#Column(name = "opt_stock")
private double opt_stock;
#Column(name = "unit")
private String unit;
#Column(name = "price_per_unit")
private double price_per_unit;
#Column(name = "supplier")
private String supplier;
-- ... getters, setters, constructors (they work fine, I can insert and get the data)
}
My controller looks like this:
#RestController
#RequestMapping(path = "api/v1/ingredient")
public class IngredientController {
private final IngredientService ingredientService;
#Autowired
public IngredientController(IngredientService ingredientService) {
this.ingredientService = ingredientService;
}
#GetMapping
public List<Ingredient> getIngredients(){
return ingredientService.getIngredients();
}
#PostMapping
public void registerNewStudent(#RequestBody Ingredient ingredient) {
ingredientService.saveIngredient(ingredient);
}
}
And my service class just uses the save() method from the JpaRepository to store new ingredients.
To this point I had the feeling, that I understood the whole thing, but when sending two post-requests to my application, each one containing an ingredient with the id "1234", and then showing all ingredients with a get request, the first ingredient just got replaced by the second one and there was no error or smth. like that in between.
Sending direct sql insert statements to the database with the same values throws an error, because the primary key constraint gets violated, just as it should be. Exactly this should have happened after the second post request (in my understanding).
What did I get wrong?
Update:
From the terminal output and the answers I got below, it is now clear, that the save() method can be understood as "insert or update if primary key is already existing".
But is there a better way around this than just error-handle every time when saving a new entry by hand?
The save method will create or update the entry if the id already exists. I'd switch to auto generating the ID when inserting, instead of manually creating the IDs. That would prevent the issue you have
When saving a new ingredient, jpa will perform an update if the value contained in the “id” field is already in the table.
A nice way through which you can achieve what you want is
ingredientRepository.findById(ingredientDTO.getIngredientId()).
ifPresentOrElse( ingredientEntity-> ResponseEntity.badRequest().build(), () -> ingredientRepository.save(ingredientDTO));
You can return an error if the entity is already in the table otherwise (empty lambda), you can save the new row
This is a downside to using CrudRepository save() on an entity where the id is set by the application.
Under the hood EntityManager.persist() will only be called if the id is null otherwise EntityManager.merge() is called.
Using the EntityManager directly gives you more fine grained control and you can call the persist method in your application when required
So I have looked at various tutorials about JPA with Spring Data and this has been done different on many occasions and I am no quite sure what the correct approach is.
Assume there is the follwing entity:
package stackoverflowTest.dao;
import javax.persistence.*;
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
public Customer(String name) {
this.name = name;
}
public Customer() {
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
We also have a DTO which is retrieved in the service layer and then handed to the controller/client side.
package stackoverflowTest.dto;
public class CustomerDto {
private long id;
private String name;
public CustomerDto(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
So now assume the Customer wants to change his name in the webui - then there will be some controller action, where there will be the updated DTO with the old ID and the new name.
Now I have to save this updated DTO to the database.
Unluckily currently there is no way to update an existing customer (except than deleting the entry in the DB and creating a new Cusomter with a new auto-generated id)
However as this is not feasible (especially considering such an entity could have hundreds of relations potentially) - so there come 2 straight forward solutions to my mind:
make a setter for the id in the Customer class - and thus allow setting of the id and then save the Customer object via the corresponding repository.
or
add the id field to the constructor and whenever you want to update a customer you always create a new object with the old id, but the new values for the other fields (in this case only the name)
So my question is wether there is a general rule how to do this?
Any maybe what the drawbacks of the 2 methods I explained are?
Even better then #Tanjim Rahman answer you can using Spring Data JPA use the method T getOne(ID id)
Customer customerToUpdate = customerRepository.getOne(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
Is's better because getOne(ID id) gets you only a reference (proxy) object and does not fetch it from the DB. On this reference you can set what you want and on save() it will do just an SQL UPDATE statement like you expect it. In comparsion when you call find() like in #Tanjim Rahmans answer spring data JPA will do an SQL SELECT to physically fetch the entity from the DB, which you dont need, when you are just updating.
In Spring Data you simply define an update query if you have the ID
#Repository
public interface CustomerRepository extends JpaRepository<Customer , Long> {
#Query("update Customer c set c.name = :name WHERE c.id = :customerId")
void setCustomerName(#Param("customerId") Long id, #Param("name") String name);
}
Some solutions claim to use Spring data and do JPA oldschool (even in a manner with lost updates) instead.
Simple JPA update..
Customer customer = em.find(id, Customer.class); //Consider em as JPA EntityManager
customer.setName(customerDto.getName);
em.merge(customer);
This is more an object initialzation question more than a jpa question, both methods work and you can have both of them at the same time , usually if the data member value is ready before the instantiation you use the constructor parameters, if this value could be updated after the instantiation you should have a setter.
If you need to work with DTOs rather than entities directly then you should retrieve the existing Customer instance and map the updated fields from the DTO to that.
Customer entity = //load from DB
//map fields from DTO to entity
So now assume the Customer wants to change his name in the webui -
then there will be some controller action, where there will be the
updated DTO with the old ID and the new name.
Normally, you have the following workflow:
User requests his data from server and obtains them in UI;
User corrects his data and sends it back to server with already present ID;
On server you obtain DTO with updated data by user, find it in DB by ID (otherwise throw exception) and transform DTO -> Entity with all given data, foreign keys, etc...
Then you just merge it, or if using Spring Data invoke save(), which in turn will merge it (see this thread);
P.S. This operation will inevitably issue 2 queries: select and update. Again, 2 queries, even if you wanna update a single field. However, if you utilize Hibernate's proprietary #DynamicUpdate annotation on top of entity class, it will help you not to include into update statement all the fields, but only those that actually changed.
P.S. If you do not wanna pay for first select statement and prefer to use Spring Data's #Modifying query, be prepared to lose L2C cache region related to modifiable entity; even worse situation with native update queries (see this thread) and also of course be prepared to write those queries manually, test them and support them in the future.
I have encountered this issue!
Luckily, I determine 2 ways and understand some things but the rest is not clear.
Hope someone discuss or support if you know.
Use RepositoryExtendJPA.save(entity). Example:
List<Person> person = this.PersonRepository.findById(0)
person.setName("Neo");
This.PersonReository.save(person);
this block code updated new name for record which has id = 0;
Use #Transactional from javax or spring framework. Let put #Transactional upon your class or specified function, both are ok. I read at somewhere that this annotation do a "commit" action at the end your function flow. So, every things you modified at entity would be updated to database.
There is a method in JpaRepository
getOne
It is deprecated at the moment in favor of
getById
So correct approach would be
Customer customerToUpdate = customerRepository.getById(id);
customerToUpdate.setName(customerDto.getName);
customerRepository.save(customerToUpdate);
I want to implement relationships from JPA to Spring JDBC. For instance, assume I have Account and Advert objects. The relationship between Account and Advert is #OneToMany according to JPA.
Account class:
public class Account {
private Long id;
private String username;
private Set<Advert> adverts = new HashSet<Advert>();
// getters + setters
}
Advert class:
public class Advert {
private Long id;
private String text;
private Account account;
// getters + setters
}
AccountMapper:
public class AccountMapper implements RowMapper<Account> {
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getLong("id"));
account.setUsername(rs.getString("username"));
return account;
}
}
Now, I am trying to create a Mapper for the Advert class. How can I map the account variable from the Advert class to a row? Many thanks
You can use Hibernate without affecting your application performance, just check out this Hibernate tutorial for hundreds of examples related too mapping entities.
As for doing that in JDBC, you need to doo the following steps:
You need to use aliases to all selected columns so that the ids columns won't clash.
You can define two row mappers and use a join from Advert to Account and pass it to the AccountMapper:
public class AdvertMapper implements RowMapper<Advert> {
public Advert mapRow(ResultSet rs, int rowNum) throws SQLException {
Advert advert = new Advert();
advert.setId(rs.getLong("advert_id"));
advert.setText(rs.getString("advert_text"));
return advert;
}
}
public class AccountMapper implements RowMapper<Account> {
private final AdvertMapper advertMapper;
public AccountMapper(AdvertMapper advertMapper) {
this.advertMapper = advertMapper;
}
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getLong("account_id"));
account.setUsername(rs.getString("account_username"));
Advert advert = this.advertMapper.mapRow(rs, rowNum);
advert.setAccount(account);
account.getAdverts().add(advert);
return account;
}
}
The AccountMapper uses the AdvertMapper to create Adverts from the joined data.
Compare this to Hibernate, where all these mappings are resolved for you.
Well if you do not use an ORM ... you have no object relation mapping ! After all the ORMs were created for that reason :-)
More seriously, ORM saves you from writing a lot of boilerplate code. Using direct JDBC instead of JPA is a code optimisation. Like any other code optimisation, it should be used when appropriate. It is relevant for :
libraries using few tables that do not want to rely on an ORM (ex: user, roles, and ACL in spring security)
identified bottlenecks in larger application
My advice should be to first use JPA or native hibernate hidden in a DAO layer. Then carefully analyze your performance problems and rewrite the most expensive parts in JDBC.
Of course, you can directly code you DAO implementations in JDBC, but it will be much longer to write.
I almost forgot the essential part : in an ORM you map classes and relations, in JDBC you write independant SQL queries.
Solving the one to one case is easy with as Vlad answered, If you want to map a one to many as your Account - Advert suggest you can't do that
with a RowMapper because you will try to map multiple rows of your ResultSet to one Account, many Advert.
You can also do that manually or you can also use http://simpleflatmapper.org that provides mapping from ResultSet to POJO with one to many support.
Beware that the bidirectional relationship is not great there if you really want those it's possible but they won't be the same instance.
Checkout
http://simpleflatmapper.org/0104-getting-started-springjdbc.html
and
https://arnaudroger.github.io/blog/2017/02/27/jooq-one-to-many.html
you will need to get a ResutlSetExtractor - it's thread safe so only need one instance -,
private final ResultSetExtractor<List<Account>> mapper =
JdbcTemplateMapperFactory
.newInstance()
.addKeys("id") // assuming the account id will be on that column
.newResultSetExtractor(Account.class);
// in the method
String query =
"SELECT ac.id as id, ac.username, ad.id as adverts_id, ad.text as adverts_text"
+ "FROM account ac LEFT OUTER JOIN advert ad ON ad.account_id = ac.id order by id "
// the order by id is important here as it uses the break on id on the root object
// to detect new root object creation
List<Account> results = template.query(query, mapper);
with that you should get a list of account with the list of adverts populated. but advert won't have the account.
I am using Objectify for Persisting entities on AppEngine.
In couple of entities, I want to store creationTime and lastUpdateTime.
My intention is that creationTime is added to an entity when it is created for the first time.
while LastUpdate time should be updated, whenever the entity is updated.
For lastUpdateTime I can easily use #PrePersist to attach a hook to modify the lastUpdateTime.
But whenever I do an update on entity, I want to retain creationTime.
Now one solution is to fetch the creationTime and then add it to my entity.
Is there any way where I dont have to fetch the creationTime just before every save?
I tried the combination of #NotSaved(IfNull) and then set creationTime to null hoping that Objectify will ignore this, but actual behaviour is that it will set creationTime=null on datastore.
For reference, my entity looks like this :
public class TestEntity {
#Id
Long id;
Date creationTime;
Date lastUpdateTime;
String mutableField;
...
// Constructor1
public TestEntity(Long id, Date date, String mutableField) {
this.id = id;
creationTime = date;
lastUpdateTime = date;
this.mutableField = mutableField;
}
// Constructor2
public TestEntity(Long id, String mutableField) {
this.id = id;
lastUpdateTime = new Date();
this.mutableField = mutableField;
}
}
Edit1 :
Sorry. I think My question was in-complete. I have modified my TestEntity definition. Let us assume that this TestEntity contains two immutable field (id, creationTime) and two mutable fields (lastUpdateTime, mutableField2).
Now depending on context, it is possible that all these five fields are sent to the client, and then client updates mutableFields and sends a request to update the mutable fields.
For simplicity of discussion, let us assume that using Session and some other way(e.g. NameSpace), we can ensure that User is allowed to modify this entity.
Now my intention is to do following :
* When entity is created for the first time, use constructor1.
* When client asks for this entity, Server sends (id and mutablefield).
* Client sends a request to update the entity with new value for mutableField alongwith Id.
* Now on the server side, I want to update the mutable field without loosing the creationTime.
Now this can be done by first fetching the entity from DataStore, then update the mutableField and then persist again. I was wondering if there is any better solution.
Use your constructor.
public class TestEntity {
public TestEntity(...your normal init params...) {
creationTime = new Date();
}
}
In the CustomerTransactions entity, I have the following field to record what the customer bought:
#ManyToMany
private List<Item> listOfItemsBought;
When I think more about this field, there's a chance it may not work because merchants are allowed to change item's information (e.g. price, discount, etc...). Hence, this field will not be able to record what the customer actually bought when the transaction occurred.
At the moment, I can only think of 2 ways to make it work.
I will record the transaction details into a String field. I feel that this way would be messy if I need to extract some information about the transaction later on.
Whenever the merchant changes an item's information, I will not update directly to that item's fields. Instead, I will create another new item with all the new information and keep the old item untouched. I feel that this way is better because I can easily extract information about the transaction later on. However, the bad side is that my Item table may contain a lot of rows.
I'd be very grateful if someone could give me an advice on how I should tackle this problem.
I would try a third option something like this.
public class Item {
private String sku;
private double currentPrice;
}
public class Customer {
private String name;
private List<Transaction> transactions;
}
public class Transaction {
private Item item;
private Customer customer;
private double pricePerItem;
private double quantity;
private String discountCode;
}
I will leave you to work out the JPA mappings.