I'm usnig Google App Engine with Java, and using Datastore via JPA.
I want to save profile image for each user:
#Entity
public class User implements Serializable {
#Id
private String userId;
private String imageKey; // saves BlobKey.getKeyString()
// ... other fields and getter/setter methods ...
}
If user wants to change their profile image, I (should) update imageKey field AND delete old image associated with old imageKey.
But, the document of Blobstore for Python says:
Deleting a Blobstore value occurs separately from datastore transactions. If you call this method during a datastore transaction, it takes effect immediately, regardless of whether the transaction commits successfully or is retried.
This seems that I can't make updating imageKey and deleting old image as one atomic action, and it also would affect to Java.
This is my attempt to do this work:
public class Engine implements ServletContextListener {
private EntityManagerFactory emf;
private BlobstoreService blobstoreService;
// Servlet will call getUser, modify returned object, and call updateUser with that object
public User getUser(final String userId) throws EngineException {
return doTransaction(new EngineFunc<User>() { // Utility method for create Entity manager and start transaction
#Override
public User run(EntityManager em) throws EngineException {
return em.find(User.class, key);
}
});
}
public void updateUser(final User user) throws EngineException {
doTransaction(new EngineFunc<Void>() {
#Override
public Void run(EntityManager em) throws EngineException {
em.merge(user);
return null;
}
});
user.purgeOldImages(blobstoreService);
}
// ... Other methods ...
}
public class User {
#Transient
private transient Set<String> oldImageList = new CopyOnWriteArraySet<>();
public void setImageKey(String imageKey) {
if (this.imageKey != null) {
oldImageList.add(this.imageKey);
}
this.imageKey = imageKey;
if (imageKey != null) {
oldImageList.remove(imageKey);
}
}
public void purgeOldImages(BlobstoreService blobService) {
Set<BlobKey> toDelete = new HashSet<>();
for (String s : oldImageList) {
toDelete.add(new BlobKey(s));
oldImageList.remove(s);
}
blobService.delete(toDelete.toArray(new BlobKey[0]));
}
// ... Other methods ...
}
I think this is neither "beautiful" nor correct code.
Is there the right way to do this?
Related
Articles are scraped from the web, so all properties (like Medium, Source etc) initially have an id of NULL and the scraped data in their fields. Before saving an article, the *toEntities()-methods make sure that the data that already exists in the DB, is reused. If the data is new, it's created via cascading in Article.
It is possible that a duplicate article is scraped which throws a "not unique exception" on save, which is good, but after that the other articles should still be saved. I guess I'm struggling with handling this exception correctly. A workaround would be to check if the entity exists but this would result in unnecessary work for 95% of the time.
In the code you see my final attempt, where I tried to save each article in its own transaction but I get these exceptions:
[SqlExceptionHelper.java:129] Duplicate entry '5628BA1A0D4115E091B5EED94FEF5840' for key 'UK_ftcejiicdrly4nl40b7lbvx6e' (the duplicate is determined by the hash value).
org.hibernate.AssertionFailure: null id in
org.observer.media.model.Article entry (don't flush the Session after
an exception occurs)
Here is the code of ArticleService. If you spot other things that are wrong with this class I'd be glad to hear them too.
#Service
public class ArticleService {
#Autowired
private ArticleRepository articleRepository;
#Autowired
private CategoryRepository categoryRepository;
#Autowired
private SourcesRepository sourcesRepository;
#Autowired
private MediumRepository mediumRepository;
private final static Logger logger = LoggerFactory.getLogger("o.o.m.s.ArticleService");
#Transactional
public void save(Iterable<Article> articles) {
for (Article article : articles) {
save(article);
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Article article) {
dataToEntities(article);
try {
articleRepository.save(article);
} catch (Exception e) {
e.printStackTrace();
}
}
#Transactional
public void delete(Iterable<Article> articles) {
if (articles == null) {
return;
}
for (Article article : articles) {
if (article.getId() != 0L) {
delete(article);
}
}
}
#Transactional
public void delete(Article article) {
article.setMedium(null);
article.setCategories(null);
article.setSources(null);
articleRepository.delete(article);
}
private void dataToEntities(Article article) {
categoriesToEntities(article);
sourcesToEntities(article);
mediumToEntity(article);
}
private void mediumToEntity(Article article) {
if (article.getMedium() == null) {
return;
}
Medium medium = mediumRepository.findByName(article.getMedium().getName());
if (medium != null) {
article.setMedium(medium);
}
}
private void sourcesToEntities(Article article) {
Set<Source> sources = new HashSet<>();
if (article.getSources() == null) {
return;
}
for (Source source : article.getSources()) {
Source entity = sourcesRepository.findByName(source.getName());
if (entity != null) {
sources.add(entity);
} else {
sources.add(source);
}
}
article.setSources(sources);
}
private void categoriesToEntities(Article article) {
Set<Category> categories = new HashSet<>();
if (article.getCategories() == null) {
return;
}
for (Category category : article.getCategories()) {
category = categoryRepository.findByName(category.getName());
categories.add(category);
}
article.setCategories(categories);
}
}
EDIT:
I followed up on the possible duplicate question and tried
the NoRollBackFor-option with all possible exceptions like below on both methods:
#Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = {ConstraintViolationException.class, MySQLIntegrityConstraintViolationException.class, DataIntegrityViolationException.class, AssertionFailure.class, RollbackException.class})
This resulted in "TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly".
I discarded the StatelessSession approach because it deviates away from the JPA spec, and because cascading would not work according to the docs. Also, it exposes a variant of Session which feels even more like a hack than just exposing the normal hibernate Session.
I am trying to understand how Hystrix works with non-fault errors and the HystrixBadRequestException, particularly in the area of validation. I use JSR-303 bean validation (Hibernate validator) for all my beans:
public class User {
#Min(1L)
private Long id;
#NotNull
#Email
private String email;
}
public class UserValidator {
private Validator validator;
// Throw exception if the user is invalid; return void otherwise.
public void validateUser(User user) {
Set<ConstraintViolation<User>> violations = validator.validate(user);
if(!violations.isEmpty()) {
return new BadEntityException(violations);
}
}
}
// Hystrix command.
public class SaveUserCommand extends HystrixCommand<User> {
public User user;
public void doSaveUser(User user) {
this.user = user;
execute();
}
#Override
protected User run() {
// Save 'user' somehow
}
#Override
protected User getFallback() {
return null;
}
}
// My service client that uses my Hystrix command.
public class UserClient {
private SaveUserCommandFactory factory = new SaveUserCommandFactory();
private UserValidator validator = new UserValidator();
public User saveUser(User user) {
SaveUserCommand saveUserCommand = factory.newSaveUserCommand();
validator.validate(user);
user = saveUserCommand.doSaveUser(user);
return user;
}
}
While this should work, I feel like the HystrixBadRequestException was created for this purpose, and I could somehow be putting the validator inside the command (not outside of it). According to the docs, this exception was intended for non-fault exceptions, including illegal arguments. I'm just not seeing how I could put my validation inside the command and leverage it (such that failed validations don't count against my metrics/stats).
It turns out you need to throw the HystrixBadRequestException inside the HystrixCommand impl. In my case, the solution was to move the validator into the SaveUserCommand#run() method:
#Override
protected void run() {
try {
validator.validate(user);
// Save user somehow
} catch(BadEntityException bexc) {
log.error(bexc);
throw new HystrixBadRequestException("Hystrix caught a bad request.", bexc);
}
}
Now, if validation fails, the outer exception is a HystrixBadRequestException and it will not count against circuit breaker stats or published metrics.
I have java web application, that is being used by multiple users. By users I mean people that actually connect to application and do stuff with it.
Every user can edit a schema, that has it's ID. At the moment, multiple users can edit same schema at the same time. I want to fix that, without using a database/table.
What I tried so far:
There's an EDIT button, that users click to edit certain schema. When they click it, a method is triggered.
protected SessionLockModSchema sessionLockModSchema = new SessionLockModSchema();
protected Model model; //schema model object
public void buttonClick(ClickEvent event) {
//button logic goes here
}
I figured I might create a bean with schema ID, when this method is triggered.
protected SessionLockModSchema sessionLockModSchema = new SessionLockModSchema();
protected Model model;
public void buttonClick(ClickEvent event) {
//button logic goes here
this.sessionLockModSchema.lockSchema(model);
}
When I have this bean created, all that I need to do is check if it exists, for the next user.
protected Model model;
public void buttonClick(ClickEvent event) {
if(!this.sessionLockModSchema.isSchemaLocked(model){
//button logic goes here
this.sessionLockModSchema.lockSchema(model);
}
}
In theory this sounded good to me, edit button wouldn't trigger if there was a bean created with that schema. But bean is not created (or at least only one user can access it). Here's sessionLockModSchema class:
public class SessionLockModSchema{
ApplicationContext context;
GenericApplicationContext ctx;
public SessionLockModSchema(){
if(ctx == null){
this.ctx = new GenericApplicationContext();
}
}
public void lockSchema(Model model){
String beanName = "model-"+model.getId();
BeanDefinitionBuilder bDBuilder = BeanDefinitionBuilder .rootBeanDefinition(String.class);
bDBuilder.setScope("prototype");
this.ctx.registerBeanDefinition(beanName, bDBuilder.getBeanDefinition());
//appcontext.close();
}
public boolean isSchemaLocked(Model model){
String beanName = "model-"+model.getId();
Object objectRef = null;
try{
//ctx.refresh();
objectRef = this.ctx.getBean(beanName);
}catch(NoSuchBeanDefinitionException e){
// TODO:
}catch(IllegalStateException e){
// TODO:
}
boolean isLocked;
if(objectRef == null){
isLocked = false;
}else{
isLocked = true;
}
return isLocked;
}
}
To clarify my question, I get IllegalStateException saying that beanFactory must be refreshed, if I do however refresh (commented ctx.refresh), I get that no such bean exists. Any advices on this? What am I doing wrong?
What about something like that (I don't see why you would need spring managed beans here):
public enum SessionLockModSchema {
INSTANCE;
private final Set<String> lockedModels = new HashSet<>;
public void lockSchema(Model model){
synchronized(lockedModels) {
lockedModels.add("" + model.getId());
}
}
public void unlockSchema(Model model){
synchronized(lockedModels) {
lockedModels.remove("" + model.getId());
}
}
public void isSchemaLocked(Model model){
synchronized(lockedModels) {
return lockedModels.contains("" + model.getId());
}
}
}
usage (something like that):
public void buttonClick(ClickEvent event) {
if (SessionLockModSchema.INSTANCE.isLocked(model) {
try {
SessionLockModSchema.INSTANCE.lockSchema(model);
// do something with "model"
} finally {
SessionLockModSchema.INSTANCE.unlockSchema(model);
}
}
}
In my Spring web app I'm using a generic dao class:
public abstract class GenericDaoImpl<T> implements GenericDao<T> {
#Override
public T create(final T t) {
this.getEntityManager().persist(t);
return t;
}
#Override
public void delete(final Object id) {
this.getEntityManager().remove(
this.getEntityManager().getReference(getEntityType(), id));
}
#Override
public T find(final Object id) {
return (T) this.getEntityManager().find(getEntityType(), id);
}
#Override
public T update(final T t) {
return this.getEntityManager().merge(t);
}
}
I implement this class for every entity in my model and it works perfectly. For example:
#Repository
public class GruppoDaoImpl extends GenericDaoImpl<Gruppo> implements GruppoDao {
}
I use these dao classes in my service layer. I have a service layer for every entity in my model, but methods for most of these classes, are the same, so I tried to create a generic service class that I can extend in the same way I do for the generic dao:
public abstract class GenericAdminServiceImpl<ENTITY extends AbstractEntity, DTO extends AbstractDto>
implements GenericAdminService<ENTITY, DTO> {
private GenericDao<ENTITY> dao;
private Class<ENTITY> entityClass;
private Class<DTO> dtoClass;
#SuppressWarnings({ "unchecked", "rawtypes" })
protected GenericAdminServiceImpl(GenericDao<ENTITY> dao) {
this.dao = dao;
//
Type t = getClass().getGenericSuperclass();
ParameterizedType pt = (ParameterizedType) t;
this.entityClass = (Class) pt.getActualTypeArguments()[0];
this.dtoClass = (Class) pt.getActualTypeArguments()[1];
}
public DTO getById(Object id) {
DTO dto = null;
ENTITY entity = dao.find(id);
if (entity != null) {
try {
dto = dtoClass.newInstance();
initDto(entity, dto);
} catch (Exception e) {
}
}
return dto;
}
public void create(DTO dto) throws ServiceOperationException {
ENTITY entity;
try {
entity = entityClass.newInstance();
initEntity(dto, entity);
Date dt = new Date();
entity.setDataUltimoAggiornamento(dt);
entity.setUtenteUltimoAggiornamento(dto.getLoggedUser());
entity.setDataInserimento(dt);
entity.setUtenteInserimento(dto.getLoggedUser());
dao.create(entity);
} catch (Exception e) {
throw new ServiceOperationException("impossibile creare entity ["
+ entityClass.getSimpleName() + "]", e);
}
}
public void update(DTO dto) throws ServiceOperationException {
ENTITY entity = dao.find(dto.getId());
if (!entityExists(entity)) {
throw new ServiceOperationException("entity non esistente ["
+ entityClass.getSimpleName() + "#" + dto.getId() + "]");
}
initEntity(dto, entity);
Date dt = new Date();
entity.setDataUltimoAggiornamento(dt);
entity.setUtenteUltimoAggiornamento(dto.getLoggedUser());
dao.update(entity);
}
public void delete(Object id) throws ServiceOperationException {
try {
dao.delete((int) id);
} catch (Exception e) {
throw new ServiceOperationException(
"impossibile eliminare entity ["
+ entityClass.getSimpleName() + "#" + id + "]", e); // TODO
}
}
protected abstract void initDto(ENTITY entity, DTO outDto);
protected abstract void initEntity(DTO dto, ENTITY outEntity);
protected abstract boolean entityExists(ENTITY entity);
}
Extending this class I just have to implement specific parts for every entity, leaving all the common stuff in the abstract/generic class.
The problem is that using the generic service, merge, persist and delete don't work. Only select seems to work and I cannot understand why...
When I run debug mode in Eclipse all seems correct. A consistent entity is passed to merge/persist methods, so why they don't work? can you help me?
UPDATE #1
This is an example of implementation:
#Service
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class GruppoServiceImplG extends
GenericAdminServiceImpl<Gruppo, GruppoDto> implements GruppoServiceG {
#Autowired
protected GruppoServiceImplG(GruppoDao gruppoDao) {
super(gruppoDao);
}
#Override
protected void initDto(Gruppo entity, GruppoDto outDto) {
outDto.setId(entity.getId());
outDto.setNome(entity.getNome());
outDto.setDescrizione(entity.getDescrizione());
outDto.setDataInizioValidita(entity.getDataInizioValidita());
outDto.setDataFineValidita(entity.getDataFineValidita());
}
#Override
protected void initEntity(GruppoDto dto, Gruppo outEntity) {
outEntity.setId(dto.getId());
outEntity.setNome(dto.getNome());
outEntity.setDescrizione(dto.getDescrizione());
outEntity.setDataInizioValidita(dto.getDataInizioValidita());
outEntity.setDataFineValidita(dto.getDataFineValidita());
}
#Override
protected boolean entityExists(Gruppo entity) {
return entity != null && entity.getId() > 0;
}
}
UPDATE #2
Following Łukasz L. suggestion, I added to all my crud methods a flush(). Now I get this exception javax.persistence.TransactionRequiredException: no transaction is in progress. What's wrong with my transaction declaration? it works fine with non-generic serices...
If you read that question about Spring and Hibernate flush behaviour, it's not easy that commiting your transaction will make also the EntityManager to save all changes. Spring and JPA (Hibernate&CO) are designed to work quite nice (from the Spring side) but nevertheless, you must assert that your entity manager will write all queries to database before commiting transaction.
The problem: JPAs like to cache. It means, they tend to avoid issuing queries. If you do SELECT, they have no choice - they must fetch some data (as long as that data portion was not fetched - like when getting single entity by ID). By INSERTs and UPDATEs - well, they CAN cache. It means, that create, merge or remove will usually not issue a query to RDBMS until you call flush() on EntityManager.
If you leave transactional block without calling flush, and entity manager is delaying operations, you'll commit transactions, by which the modifying queries were not issued!
Just make sure to call EntityManager.flush() at least at the end of the transactional method. You can also call it after each DML operation, it's your choice (I prefer that way because it gives me full control in which order the DML queries are issued by JPA, if you heavily uses DB constraints/triggers, it can be essential).
#Transactional
public void myTransactionalMethod() {
getEntityManager().persist(a); // I've made some JPA change, that is not issued to DB yet
...
// I'm doing something more
...
getEntityManager().flush(); // the last moment to flush, after that instruction I leave transactional context
}
Following Łukasz L. suggestion I discovered the actual issue in my generic class.
Transaction declaration was wrong. I set #Transactional(propagation = Propagation.REQUIRES_NEW) only in concrete service class.
I solved this way:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public abstract class GenericAdminServiceImpl<ENTITY extends AbstractEntity, DTO extends AbstractDto>
implements GenericAdminService<ENTITY, DTO> {
// ...
}
And (in concrete implementation):
#Service
#Transactional
public class GruppoServiceImplG extends
GenericAdminServiceImpl<Gruppo, GruppoDto> implements GruppoServiceG {
// ...
}
I have followed a tutorial on dynamic datasource routing tutorial in Spring. For that I have to extend AbstractRoutingDataSource to tell spring which datasource to get, so I do:
public class CustomRouter extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
}
Everything goes fine till I find the class responsible for keeping the value of the customerType (it should be the same during the whole session):
public class CustomerContextHolder {
private static final ThreadLocal<Integer> contextHolder = new ThreadLocal<Integer>();
public static void setCustomerType(Integer customerType) {
contextHolder.set(customerType);
}
public static Integer getCustomerType() {
return (Integer) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
This creates a thread-bound variable customerType, but I have a web application with spring and JSF I don't think with threads but with sessions. So I set it in the login page with thread A (View), but then thread B (Hibernate) request the value to know what datasource to use, it is null indeed, because it has a new value for this thread.
Is there any way to do it Session-bounded instead of Thread-bounded?
Things I have tried so far:
Inject the CustomRouter in the view to set it in the session: Not working, it causes a cycle in dependecies
Replace the ThreadLocal with an Integer: Not working, the value is always set by the last user logged in
Is FacesContext.getCurrentInstance() working? If so then you may try with this:
public class CustomerContextHolder {
private static HttpSession getCurrentSession(){
HttpServletRequest request = (HttpServletRequest)FacesContext.getCurrentInstance()
.getExternalContext().getRequest();
return request.getSession();
}
public static void setCustomerType(Integer customerType) {
CustomerContextHolder.getCurrentSession().setAttribute("userType", customerType);
}
public static Integer getCustomerType() {
return (Integer) CustomerContextHolder.getCurrentSession().getAttribute("userType");
}
public static void clearCustomerType() {
contextHolder.remove(); // You may want to remove the attribute in session, dunno
}
}