External object linked through foreign key in hibernate and MySql - java

I'm using Spring data with Hibernate and MySql and I have a doubt.
My entity is
#Entity
#Table(name = "car", catalog = "DEMO")
public class Car implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idCar;
#JsonBackReference
private CarType carType;
#JsonBackReference
private Fleet fleet;
private String id;
private int initialKm;
private String carChassis;
private String note;
#JsonManagedReference
private Set<Acquisition> acquisitions = new HashSet<Acquisition>(0);
with get and set method.
Sometimes, I need external object as carType, another entity.
If I use this webservice
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public String getCars(#PathVariable int idFleet, Model model){
try{
model.addAttribute("carsList",fleetAndCarService.findCarsByIdFleet(idFleet));
//Modal parameter
model.addAttribute("carTypeList",fleetAndCarService.getCarsType());
model.addAttribute("fleetApplication",fleetAndCarService.getFleetById(idFleet));
model.addAttribute("carForm", new CarForm());
model.addAttribute("error",false);
}catch (Exception e){
LOG.error("Threw exception in FleetAndCarControllerImpl::getCars : " + ErrorExceptionBuilder.buildErrorResponse(e));
model.addAttribute("error",true);
}
return "cars";
}
from my html page I can retrieve carType.idCarType,but if I use this
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public #ResponseBody TableUI getCars(#PathVariable int idFleet) {
TableUI ajaxCall=new TableUI();
try {
ajaxCall.setData(fleetAndCarService.findCarsByIdFleet(idFleet));
return ajaxCall;
} catch (QueryException e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in FleetAndCarControllerImpl::addCar :" + errorResponse.getStacktrace());
return ajaxCall;
}
}
where TableUi has only a field data where I put the result to use it into datatables, I don't have carType and fleet. Why? Do I have to use Hibernate.initialize, and how so it is a list?Thansk,regards
Also this update doesn't work:
#Override
#Transactional
public List<Car> findByFleetIdFleet(int idFleet) {
List<Car> carList= carRepository.findByFleetIdFleet(idFleet);
for (Car car:carList)
Hibernate.initialize(car.getCarType());
return carList;
}

You could call Hibernate.initialize on each element
Collection<Car> cars = fleetAndCarService.findCarsByIdFleet(idFleet);
for(Car car : cars) {
Hibernate.initialize(car.getCarType());
Hibernate.initialize(car.getFleet());
}
ajaxCall.setData();
return ajaxCall;
This would be a good starting point and would allow you to move forwards. At high scales however this could become a performance bottleneck as it will perform a query with each call to initialize so you will have 2*n queries to the database.
For maximum performance you will have several other options:
Iterate through the cars and build up a list of IDs and then query for the car types by ID in a single query with the list of IDs. Do the same for the fleets. Then call Hibernate.initialize. The first two queries will populate the persistence context and the call to initialize will not need to go to the database.
Create a special query for this call which fetch joins the properties you will need.
Setup batch fetching which will fetch the cards and fleets in batches instead of one car/fleet per query.
Use a second level cache so the initialization causes Hibernate to pull from the cache instead of the database.
Describing these options in details is beyond the scope of a single question but a good place to start would be Hibernate's documentation on performance.

Related

Spring Data Elasticsearch: Multiple Index with same Document

I'm using spring-data-elasticsearch and for the beginning everything works fine.
#Document( type = "products", indexName = "empty" )
public class Product
{
...
}
public interface ProductRepository extends ElasticsearchRepository<Product, String>
{
...
}
In my model i can search for products.
#Autowired
private ProductRepository repository;
...
repository.findByIdentifier( "xxx" ).getCategory() );
So, my problem is - I've the same Elasticsearch type in different indices and I want to use the same document for all queries. I can handle more connections via a pool - but I don't have any idea how I can implement this.
I would like to have, something like that:
ProductRepository customerRepo = ElasticsearchPool.getRepoByCustomer("abc", ProductRepository.class);
repository.findByIdentifier( "xxx" ).getCategory();
Is it possible to create a repository at runtime, with an different index ?
Thanks a lot
Marcel
Yes. It's possible with Spring. But you should use ElasticsearchTemplate instead of Repository.
For example. I have two products. They are stored in different indices.
#Document(indexName = "product-a", type = "product")
public class ProductA {
#Id
private String id;
private String name;
private int value;
//Getters and setters
}
#Document(indexName = "product-b", type = "product")
public class ProductB {
#Id
private String id;
private String name;
//Getters and setters
}
Suppose if they have the same type, so they have the same fields. But it's not necessary. Two products can have totally different fields.
I have two repositories:
public interface ProductARepository extends ElasticsearchRepository<ProductA, String> {
}
public interface ProductBRepository
extends ElasticsearchRepository<ProductB, String> {
}
It's not necessary too. Only for testing. The fact that ProductA is stored in "product-a" index and ProductB is stored in "product-b" index.
How to query two(ten, dozen) indices with the same type?
Just build custom repository like this
#Repository
public class CustomProductRepositoryImpl {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public List<ProductA> findProductByName(String name) {
MatchQueryBuilder queryBuilder = QueryBuilders.matchPhrasePrefixQuery("name", name);
//You can query as many indices as you want
IndicesQueryBuilder builder = QueryBuilders.indicesQuery(queryBuilder, "product-a", "product-b");
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(builder).build();
return elasticsearchTemplate.query(searchQuery, response -> {
SearchHits hits = response.getHits();
List<ProductA> result = new ArrayList<>();
Arrays.stream(hits.getHits()).forEach(h -> {
Map<String, Object> source = h.getSource();
//get only id just for test
ProductA productA = new ProductA()
.setId(String.valueOf(source.getOrDefault("id", null)));
result.add(productA);
});
return result;
});
}
}
You can search as many indices as you want and you can transparently inject this behavior into ProductARepository adding custom behavior to single repositories
Second solution is to use indices aliases, but you had to create custom model or custom repository too.
We can use the withIndices method to switch the index if needed:
NativeSearchQueryBuilder nativeSearchQueryBuilder = nativeSearchQueryBuilderConfig.getNativeSearchQueryBuilder();
// Assign the index explicitly.
nativeSearchQueryBuilder.withIndices("product-a");
// Then add query as usual.
nativeSearchQueryBuilder.withQuery(allQueries)
The #Document annotation in entity will only clarify the mapping, to query against a specific index, we still need to use above method.
#Document(indexName="product-a", type="_doc")

hibernate findbyid causes update?

I faced with a very strange behavior in my web app with spring 3 and hibernate-core 3.5.1-Final.
For simplicity i provide my code..
if(ripid!=null){ //Parameter
Appuntamento apDaRip = appuntamentoService.findById(ripid);
if(apDaRip.getIdpadre()!=null){
apDaRip.setNota("RIPROGRAMMATO n."+ripid.toString()+"\n"+apDaRip.getNota());
apDaRip.setIdpadre(apDaRip.getIdpadre());
}else{
apDaRip.setNota("RIPROGRAMMATO n."+ripid.toString()+"\n"+apDaRip.getNota());
apDaRip.setIdpadre(ripid);
}
try{
apDaRip.setOrarioinizio(null);
apDaRip.setDurata(null);
//apDaRip.setIdappuntamento(null);
}catch(Exception e){e.printStackTrace();}
map.put("appuntamento", apDaRip);
}
di = datiintranetService.findById(DatiintranetService.PASS_X_INTERVENTI);
map.put("passinterventi", di.getBoolean());
The idea behind is to use some data of an object "Appuntamento" for produce a new one.
So i'm going to change some value and before send the object to my view (jsp) i fetch other data by calling findbyid. This cause an update to the Appuntamento object... Off course i don't want this behavior. Someone can have an explanation of this?
Edit-1
Here's the Dao
#Transactional
public class DatiintranetService {
private DatiintranetDAO datiintranetDAO;
public void setDatiintranetDAO(DatiintranetDAO datiintranetDAO) {
this.datiintranetDAO = datiintranetDAO;
}
public DatiintranetDAO getDatiintranetDAO() {
return datiintranetDAO;
}
public Datiintranet findById(Integer id) {
return datiintranetDAO.findById(id);
}
}
and For Appuntamento class I provide to you a snapshot
#Entity
#Table(name = "appuntamento", schema = "public")
public class Appuntamento implements java.io.Serializable {
#Id
#SequenceGenerator(name="appuntamentoID", sequenceName="appuntamento_idappuntamento_seq",allocationSize =1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="appuntamentoID")
#Column(name = "idappuntamento", unique = true, nullable = false)
public Integer getIdappuntamento() {
return this.idappuntamento;
}
}
Edit-2
IF i move thoese two row above the if statement no update occur.
di = datiintranetService.findById(DatiintranetService.PASS_X_INTERVENTI);
map.put("passinterventi", di.getBoolean());
If you query for an entity and change the entity, the default behavior is to persist those changes via an update to the database. This is usually what you want to happen, but obviously not in all cases.
If you want to avoid the update, you need to detach the entity by calling session.evict(apDaRip) where session is a reference to the hibernate session (see Session.evict()). You probably want to evict the entity right after you get it (immediately following the call to findById).

How to get old entity value in #HandleBeforeSave event to determine if a property is changed or not?

I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}

How do I correctly paginate in Hibernate with nested objects with ManyToMany associations?

Ok, so I have the following (abbreviated) 3 entity and HibernateUtil classes.
public class Tag {
#Id
BigDecimal id;
String tag
#ManyToMany( mappedBy="tags" )
List<Label> labels;
}
public class Label {
#Id
BigDecimal id;
String label;
#ManyToMany( targetEntity=Tag.class )
List<Tag> tags;
}
public class Data {
#Id
BigDecimal id;
BigDecimal data;
#ManyToOne
Label label;
}
public class HibernateUtil {
public static List pagedQuery(DetachedCriteria detachedCriteria, Integer start, Integer size) throws WebApplicationException {
Session session = getSession();
try {
Transaction transaction = session.beginTransaction();
List records = detachedCriteria.getExecutableCriteria(session)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.setFirstResult(start)
.setMaxResults(size)
.list();
transaction.commit();
return records;
} catch (Exception e) {
// Place Logger here...
throw new WebApplicationException(e);
} finally {
session.close();
}
}
}
The issue I have is that when I try to query the Data class with the HibernateUtil.pagedQuery( detatchedCriteria, start, size ), my result list doesn't match the size parameter. I have found that the reason for this is the way hibernate builds the query to include the tags (Data.Label.Tags).
For instance, when a Label has more than one associated Tags the result list for the Data object subquery used in the complete paginated query would look like the following (I found this by parsing the sql Hibernate spits out to the console)
Data-1;Label:Tag-1
Data-1;Label;Tag-2
Data-2;Label;Tag-1
Data-2;Label;Tag-2
etc...
If I were to call this with size=3, then the returned result set would be
Data-1;Label:Tag-1
Data-1;Label;Tag-2
Data-2;Label;Tag-1
However, Hibernate would then group the first two rows together (since they're the same Data object), and my returned List object would have a size of 2 (Data-1 & Data-2)
I attempted to replace the setResultTransformer method with a Projection approach that I found through Google, but that then only returned the id's of the Data objects.
Does anyone have any advice for me? I'm not sure where to go from here...
You are facing a common problem paginating with hibernate. The resultTransformer is applied in the "Java" side, so the pagination has already been made on the DB side.
The simplest (maybe not the most optimized) is to do two queries, one with the projection and pagination (like the one you already did) and another using the projection id's. Here is an example:
//get the projection
Criteria criteria = factory.getCurrentSession().createCriteria(getEntityClass());
criteria.setProjection(Projections.distinct((Projections.projectionList().add(Projections.id()).add(Projections.property("name")))));
//paginate the results
criteria.setMaxResults(pageSize);
criteria.setFirstResult(first);
List<Object[]> idList = criteria.list();
//get the id's from the projection
List<Long> longList = new ArrayList<Long>();
for (Object[] long1 : idList) {
Object[] record = long1;
longList.add((Long) record[0]);
}
if (longList.size() > 0) {
//get all the id's corresponding to the projection,
//then apply distinct root entity
criteria = factory.getCurrentSession().createCriteria(getEntityClass());
criteria.add(Restrictions.in("id", longList));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
} else {
//no results, so let's ommit the second query to the DB
return new ArrayList<E>();
}
return criteria.list();

Saving entities fetched from a query with hibernate

I hava a basic Hibernate/JPA question. I want to find a best practice solution for saving entities. I have a List of Entities and many of them might be altered so I want to save them all at once.
I believe everything is pretty much standard. (Just example code for readability reasons)
Entity: Car
#Entity
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private id;
private String model;
// .... Setter
// .... Getter
}
Service Class: CarService
#Named
#Transactional
public class CarServiceImpl implements CarService {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Car> findAll() {
TypedQuery<Car> q = entityManager.createQuery(
"FROM Car", Car.class);
return q.getResultList();
}
#Override
public void saveEntity (Car car) {
/* What exactly am I doing here? */
}
}
Controller: CarEditController
#Named
public class CarEditController implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
private CarService carService;
private List<Car> cars;
public List<Car> getCars () {
return carService.findAll();
}
public void setCars (List<Car> cars) {
this.cars = cars;
}
public void btn_newClick() {
Car newCar = new Car();
car setModel("BMW");
cars.add(newCar);
}
public void btn_saveClick() {
for (Car car : cars) {
carService.saveEntity(car);
}
}
}
I found quite a few ways of saving the entity. The obvious are entityManager.merge(car) for existing entities and entityManager.persist(car) for new ones. In theory thats easy but how do I know which entity is new?
The documentation suggests entityManager.flush(), which in theory updates all existing and inserts all new entities. It works but only if I fetch the entity with find().
The Question:
I want to fetch all entities in one step, add new ones and then save them all in one methode (btn_saveClick()). How is this task best accomplished?
Just check if the #Id is been set.
#Override
public void saveEntity (Car car) {
if (car.getId() == null) {
entityManager.persist(car);
} else {
entityManager.merge(car);
}
}
More common approach, however, is to offer separate create() and update() service methods.
I'm not familiar with JPA but in hibernate there is session.saveOrUpdate()
for(Car car : existingAndNewCars)
{
session.saveOrUpdate(car);
}
Update:
As i understand JPA, its merge is like session.merge which is totally different as it doesn't track changes to object supplied to it while persist/save/update/saveOrUpdate would track subsequent changes to car, leading to subtle differences
Update:
since you use the same entitymanager it should suffice to
#Override
public void saveEntity (Car car) {
if (car.getId() == null) {
entityManager.persist(car);
}
without the subtle difference of persist and merge
The flush operation will operate on all entities in the current Hibernate session - new entities are inserted and existing entities are updated if they have changed.
You need to ensure that all entities are attached to the session. You do this by using merge as you correctly say. After you have loaded all of the entities the session is closed when the transaction ends. Your objects are then in a detached state i.e. have been persisted but are no longer attached to a session.
I would amend your logic so that your carService#save takes a List. It can then call merge on each one (attaching them to the session). Then when your transaction ends Hibernate will flush all changes to the database at once.

Categories

Resources