How to set unique rows when persist in JPA? - java

My question is: is there any way to set unique row for rows created by "new" keyword?
I mean like this:
Product product = Product.builder()
.eancode("EAN-1234")
.externalId("123123")
.producerPartNumber("123123")
.name("VERY GOOD LAPTOP")
.vendor(new Vendor("LENOVO", "www.lenovo.com"))
.priceDetails(new PriceDetails(
new BigDecimal("19.99"),
new BigDecimal("20.00"),
new BigDecimal("21.00"),
Currency.PLN))
.build();
I want Vendor entity to be unique because now I've got something like this (when I multiply run this code):
id name url
1 LENOVO lenovo.com
2 LENOVO lenovo.com
I just want it to check it first if that name exist yet. Should I use #EmbeddedId in some way?
Edit: look now
#Override
public Long save(Product item) {
EntityManager em = getEntityManager();
em.getTransaction().begin();
//that line throws PersistentObjectException: detached entity passed to persis
em.persist(item); //cascade is set as PERSIST so why isn't it work
em.getTransaction().commit();
em.close();
return item.getId();
}

You need a VendorRepository contains a method like this Optional<Vendor> getVendorByNameAndUrl(String name, String url).
Then
VendorRepository vendorRepo;
#Transactional
public void saveProduct() {
Product product = Product.builder()
.eancode("EAN-1234")
.externalId("123123")
.producerPartNumber("123123")
.name("VERY GOOD LAPTOP")
.vendor(findVendorOrCreateNew("LENOVO", "www.lenovo.com"))
.priceDetails(new PriceDetails(
new BigDecimal("19.99"),
new BigDecimal("20.00"),
new BigDecimal("21.00"),
Currency.PLN))
.build();
// ...
}
Vendor findVendorOrCreateNew(String name, String url) {
return vendorRepo.getVendorByNameAndUrl(name, url)
.orElse(new Vendor(name, url));
}

Related

External object linked through foreign key in hibernate and MySql

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.

Trouble deleting entity with specific property set in Java GAE

I have the entities created like this:
public String addNewStockName(String newStock) throws DelistedException {
Entity stock = new Entity("Stocks");
stock.setProperty("Name", newStock);
ds.put(stock);
return "OK";
}
Trying delete the specific entity like this:
public String deleteStockName(String stockName){
Key key = KeyFactory.createKey("Stocks", stockName);
ds.delete(key);
return "OK";
}
And it does not delete the entity which has property 'stockName'. Why?
If you want to create an entity that you can fetch by stockName, you need something like
public String addNewStockName(String stockName) throws DelistedException {
Key key = KeyFactory.createKey("Stocks", stockName);
Entity stock = new Entity(key);
stock.setProperty("foo", "bar");
ds.put(stock);
return "OK";
}
You can then use your deleteStockName() method as is. This of course assumes your key name is unique, but it also means you can always fetch the Stock by key, rather than query.
Your Stocks entity has a property named "Name". That is not the same as the key name.
You have to perform a query to get the entities or entity keys matching the filter of "Name=?".
Something like this:
public String deleteStockName(String stockName) {
DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
Filter f = new FilterPredicate("Name", FilterOperator.EQUAL, stockName);
Query q = new Query("Stocks").setFilter(f).setKeysOnly();
List<Entity> results = ds.prepare(q)
.asList(FetchOptions.Builder.withDefaults());
if (results.isEmpty())
return "Not Found!";
ds.delete(results.get(0).getKey());
return "OK";
}

JPA many-to-many relationship, keeping a list of ids

I have 2 POJO classes in Java: Phrase and Tag, in a many-to-many relationship:
Phrase.java
#Entity
#EntityListeners(value={PhraseListener.class})
public class Phrase {
#Id
#GeneratedValue
#Column(name="id")
private Long phraseId;
#Column(nullable=false)
private String text;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="phrase_has_tag",
joinColumns={#JoinColumn(name="phrase_id",referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="tag_uname",referencedColumnName="uname")})
private Collection<Tag> tagObjects;
#Transient
private Set<String> tags;
public Phrase() {
tagObjects = new ArrayList<Tag>();
tags = new HashSet<String>();
}
// getters and setters
// …
public void addTagObject(Tag t) {
if (!getTagObjects().contains(t)) {
getTagObjects().add(t);
}
if (!t.getPhrases().contains(this)) {
t.getPhrases().add(this);
}
}
public void addTag(String tagName) {
if (!getTags().contains(tagName)) {
getTags().add(tagName);
}
}
Tag.java
#Entity
public class Tag {
#Id
#Column(name="uname")
private String uniqueName;
private String description;
#ManyToMany(mappedBy="tagObjects")
private Collection<Phrase> phrases;
public Tag() {
phrases = new ArrayList<Phrase>();
}
// getters and setters
// …
The primary key for the tag entity is its name. I want to keep in Phrase.java a Set of tag names "synchronized" with the tagObjects field of the many-to-many relationship, and viceversa. For doing this, I add a listener to Phrase.java:
public class PhraseListener {
#PostLoad
public void postLoad(Phrase p) {
System.out.println("In post load");
for (Tag tag : p.getTagObjects()) {
p.addTag(tag.getUniqueName());
}
}
#PrePersist
public void prePersist(Phrase p) {
System.out.println("In pre persist");
EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestJPA");
EntityManager em = emf.createEntityManager();
for (String tagName : p.getTags()) {
Tag t = em.find(Tag.class, tagName);
if (t == null) t = new Tag(tagName);
p.addTagObject(t);
}
}
}
which after loading, it creates the set of tag names from the tag objects and before persisting it reads the set of tag names, and fetch or create tag objects.
My problem is that if I try to create multiple phrases which share tags, JPA instead of only creating the relationship (insert into the join table) it also create tag objects which violate primary key constraint.
transaction.begin();
Phrase p = new Phrase("Never ask what sort of computer a guy drives. If he's a Mac user, he'll tell you. If not, why embarrass him?", "Tom Clancy");
p.addTag("apple");
p.addTag("macintosh");
em.persist(p);
transaction.commit();
transaction.begin();
p = new Phrase("It's better to be a pirate than to join the Navy.", "Steve Jobs");
p.addTag("apple");
em.persist(p);
transaction.commit();
Exception in thread "main" javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: [SQLITE_CONSTRAINT] Abort due to constraint violation (column uname is not unique)
Error Code: 0
Call: INSERT INTO TAG (uname, DESCRIPTION) VALUES (?, ?)
bind => [apple, null]
You haven't shown your addTag method in Phrase, but I assume that you have somewhere in there an expression new Tag() and it would look somehow similar to this:
public void addTag(String tagName) {
Tag tag = new Tag();
tag.setUniqueName(tagName);
tag.getPhrases().add(this);
this.tagObjects.add(tag);
}
In this case the method addTag will create new object of type Tag everytime the method is called, which will result in different entries in the relational table, cause Hibernate persists the whole objects, not only particular fields of theirs, regardless if these fields are primary keys or not.
After calling the method addTag two times, you will create two different objects and Hibernate could not know if those two objects relate to the same entry in the DB or not. This means that even though they have the same uniqueName, they could have a different description.
Imagine the following scenario:
transaction.begin();
Phrase p = new Phrase("Never ask what sort of computer a guy drives. If he's a Mac user, he'll tell you. If not, why embarrass him?", "Tom Clancy");
Tag t = new Tag();
t.setUniqueName("apple");
t.setDescription("This is an example apple");
p.getTagObjects().add(t);
em.persist(p);
transaction.commit();
transaction.begin();
p = new Phrase("It's better to be a pirate than to join the Navy.", "Steve Jobs");
t = new Tag();
t.setUniqueName("apple");
t.setDescription("Another description of the apple");
em.persist(p);
transaction.commit();
With this example the difference should be more obvious and should illustrate why it is impossible to Hibernate to know when are you referring to the same entry in the DB with two or more different objects.
As a solution I would suggest you to change the method addTag so that it has the following signature public void addTag(Tag tag) {... and keep track of the existing tags somewhere centralized or you can try out em.merge(p); instead of em.persist(p);

em.Persist(book) wont update entity

I have a new JPA entity with auto-generated id and I persist it.
After that I want to get it again, modify it and persist the changes.
The new entity gets persisted into the database with an auto-generated id but the entity's bookid remains null.
This way I cannot use the getBook(id) function to find it again.
#Stateless
public class BookManager implements BookManagerRemote {
#PersistenceContext
EntityManager em;
#EJB
Authenticator auth;
public BookManager() {
}
#Override
public void addBook(String user, Konyv konyv) throws InsufficentPrivilegesException {
if (auth.isAdmin(user)) {
em.persist(konyv);
} else {
throw new InsufficentPrivilegesException();
}
}
#Override
public Konyv getBook(String user, Integer bookid) throws InsufficentPrivilegesException {
if (auth.isAdmin(user)) {
return em.find(Konyv.class, bookid);
} else {
throw new InsufficentPrivilegesException();
}
}
}
-
Book mistypedBook = new Book("Stanislaw Lem", "Kibeliada");
bookmanager.addBook(name, mistypedBook);
Book whopsBook = bookmanager.getBook(name, mistypedBook.getBookid()/*<-ID IS NULL*/);
How can I sync the newly persisted entity with the database?
I've seen JPA Container managed entity update after persist question, but I'm trying to use the entity's id after the method ended. Shouldn't it have the id by then?
Your addBook method needs to return the persisted entity.
em.persist(konyv);
konyv = em.merge(konyv);
return konyv;
The entity returned will contain the generated id. As you have the persisted entity back, you won't need to call the getBook method.

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();

Categories

Resources