Trouble deleting entity with specific property set in Java GAE - java

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";
}

Related

How to set unique rows when persist in JPA?

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

How can I update cache with CachePut?

My #Cacheable method has next signature:
#Component
public class UpcomingFilter implements Filter<Entity> {
#Cacheable(value = {"upcoming"})
#Override
public List<Entity> filter(int limit) {
//retrieve from repository
}
}
This filter use the reporisoty, take limit as parameter for pagination and return List of Entities.
I'm trying to update cache when add Entity to the system:
#CachePut(value={"upcoming", "popular", "recentlyAdded", "recommendations", "thisWeek", "topRated"})
public Entity addEntity(RequestDto dto, User user) {
//do work, create and save entity to repository
return entity;
}
But after adding new entity to the system it is not updated. Filters returns old values.
I saw examples, where for CachePut and Cacheable the word 'key' is used, but how can I add
#Cacheable(key="#entity.id")
to the Filter signature ?
UPDATE
Tried to add my key:
#CachePut(value={"upcoming","popular", "recentlyAdded", "recommendations", "thisWeek", "topRated"},
key = "#root.target.FILTER_KEY", condition = "#result != null")
public Entity addEntity(RequestDto dto, User user) {
//do work, create and save entity to repository
return entity;
}
and also add key to #Cacheable:
public static final String FILTER_KEY = "filterKey";
#Cacheable(value = {"recentlyAdded"}, key = "#root.target.FILTER_KEY")
#Override
public List<Entity> filter(int limit) {
and than I get
java.lang.ClassCastException: com.java.domain.Entity cannot be cast to
java.util.List
Instead #CachePut the #CacheEvict should be used.
It works for me:
#CacheEvict(value={"upcoming", "popular", "recentlyAdded", "recommendations", "thisWeek", "topRated"},
allEntries = true, condition = "#result != null")
public Entity addEntity(RequestDto dto, User user) {
//do work, create and save entity to repository
return entity;
}

Selecting generic primary key with CriteriaQuery

When migrating from Hibernate Criteria api to CriteriaQuery I ran into a generic DAO for a abstract class that has a where on a common field but does a select on their id, even if the ids are totally different per class.
The old projection looks like this
criteria.setProjection(Projections.id());
Is there any way to do this in a similar way with CriteriaQuery?
Edit: Full criteria code
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(MyEntity.class);
detachedCriteria.add(Restrictions.in("accountID", accounts));
detachedCriteria.setProjection(Projections.id());
EntityManager em = ...;
Criteria criteria = detachedCriteria.getExecutableCriteria((Session) em.getDelegate());
List<Integer> list = criteria.list();
I just managed to find it on my own.
criteriaQuery.select(
root.get(entityRoot.getModel().getDeclaredId(int.class))
);
Combining answers:
https://stackoverflow.com/a/16911313
https://stackoverflow.com/a/47793003
I created this method:
public String getIdAttribute(EntityManager em, String fullClassName) {
Class<? extends Object> clazz = null;
try {
clazz = Class.forName(fullClassName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Metamodel m = em.getMetamodel();
IdentifiableType<T> of = (IdentifiableType<T>) m.managedType(clazz);
return of.getId(of.getIdType().getJavaType()).getName();
}
then I have the entity manager injected
#PersistenceContext
private EntityManager em;
I get the root entity primary key like that:
String rootFullClassName = root.getModel().getJavaType().getName();
String primaryKeyName = getIdAttribute(em, rootFullClassName);
and I get the primary keys referenced on attributes like that:
return (Specification<T>) (root, query, builder) -> {
Set<Attribute<? super T, ?>> attributes = root.getModel().getAttributes();
for (Attribute a: attributes) {
if(a.isAssociation()) {
Path rootJoinGetName = root.join(a.getName());
String referencedClassName = rootJoinGetName.getJavaType().getName();
String referencedPrimaryKey = getIdAttribute(em, referencedClassName);
//then I can use it to see if it is equal to a value (e.g
//filtering actors by movies with id = 1 - in
//this case referencedPrimaryKey is "id")
Predicate p = rootJoinGetName.get(referencedPrimaryKey).in(1);
}
}
}
In this way I don't need to know the type of the primary key/referenced key in advance as it can be derived through the Entity Manager Meta Model. The above code can be used with CriteriaQuery as well as Specifications.

Update single field using spring data jpa

I'm using spring-data's repositories - very convenient thing but I faced an issue. I easily can update whole entity but I believe it's pointless when I need to update only a single field:
#Entity
#Table(schema = "processors", name = "ear_attachment")
public class EARAttachment {
private Long id;
private String originalName;
private String uniqueName;//yyyy-mm-dd-GUID-originalName
private long size;
private EARAttachmentStatus status;
to update I just call method save. In log I see the followwing:
batching 1 statements: 1: update processors.ear_attachment set message_id=100,
original_name='40022530424.dat',
size=506,
status=2,
unique_name='2014-12-16-8cf74a74-e7f3-40d8-a1fb-393c2a806847-40022530424.dat'
where id=1
I would like to see some thing like this:
batching 1 statements: 1: update processors.ear_attachment set status=2 where id=1
Spring's repositories have a lot of facilities to select something using name conventions, maybe there is something similar for update like updateForStatus(int status);
You can try something like this on your repository interface:
#Modifying
#Query("update EARAttachment ear set ear.status = ?1 where ear.id = ?2")
int setStatusForEARAttachment(Integer status, Long id);
You can also use named params, like this:
#Modifying
#Query("update EARAttachment ear set ear.status = :status where ear.id = :id")
int setStatusForEARAttachment(#Param("status") Integer status, #Param("id") Long id);
The int return value is the number of rows that where updated. You may also use void return.
See more in reference documentation.
Hibernate offers the #DynamicUpdate annotation. All we need to do is to add this annotation at the entity level:
#Entity(name = "EARAttachment ")
#Table(name = "EARAttachment ")
#DynamicUpdate
public class EARAttachment {
//Code omitted for brevity
}
Now, when you use EARAttachment.setStatus(value) and executing "CrudRepository" save(S entity), it will update only the particular field. e.g. the following UPDATE statement is executed:
UPDATE EARAttachment
SET status = 12,
WHERE id = 1
You can update use databind to map #PathVariable T entity and #RequestBody Map body. And them update body -> entity.
public static void applyChanges(Object entity, Map<String, Object> map, String[] ignoreFields) {
map.forEach((key, value) -> {
if(!Arrays.asList(ignoreFields).contains(key)) {
try {
Method getMethod = entity.getClass().getMethod(getMethodNameByPrefix("get", key));
Method setMethod = entity.getClass().getMethod(getMethodNameByPrefix("set", key), getMethod.getReturnType());
setMethod.invoke(entity, value);
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
});
}

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