[RESOLVIDO]Hibernate - re-saved by cascade

Olá galera, não costumo postar a não ser que fique encalhado em algo por um tempo relativamente grande rsrs basicamente não consigo inserir/alterar/deletar ao mesmo tempo meu objeto Foto! :lol: segue o resumo:
Tenho a classe Album 1-N Foto, eu persisto o objeto foto primeiro(realizo um upload assíncrono) e ao terminar salvo as fotos em uma List no objeto Album(onde, as fotos já armazenadas, apenas fazem um update com a FK do Album), estou fazendo um mapeamento bidirecional, o problema ocorre quando defino o cascadeType como ALL, ele insere e altera porém não deleta causando uma [color=red]org.hibernate.ObjectDeletedException[/color](a stacktrace está no fim do código), se defino o cascadeType como MERGE, ele altera e deleta, mas NÃO insere, os registros de fotos não são atualizados com a FK do Album!
Em minhas pesquisas encontrei algumas possíveis soluções como:
*Utilizar CascadeType.DELETE_ORPHAN
*remover suas relações nos parents objects

Também já tentei todas as combinações possíveis de cascadeType além do ALL, para ver se alguma dava certo(Ex: PERSIST+MERGE+REMOVE[ou DELETE]) sem sucesso, é a primeira vez que tento o relacionamento OneToMany

Acredito muito que meu erro seja exatamente esse, não estou limpando a foto em seus devidos lugares porém, removo-a do objeto album, não sei onde mais posso removê-la, se não for isso devo estar mapeando errado, se puderem me dar uma luz agradeço :lol:

Segue meu mapeamento e o método assíncrono de apagar as fotos que estou utilizando:

[color=red]Foto.java[/color]

public class Foto extends ValidatorForm{
	private static final long serialVersionUID = 2345678;

	private long id;
	private String arquivo;
	private FormFile file = null;
	private Album album;

	public Foto() {
	}
	
	public Foto(String arquivo) {
		this.arquivo = arquivo;
	}

	@Id
	@GeneratedValue
	@Column(name="id")
	public long getId() {
		return id;
	}

	@Column(name = "strArquivo", nullable = false)
	public String getArquivo() {
		return arquivo;
	}

	public void setArquivo(String arquivo) {
		this.arquivo = arquivo;
	}

	@Transient
	public FormFile getFile() {
		return file;
	}

	public void setFile(FormFile file) {
		this.file = file;
		String caminho = Info.getProjectPath("/")+"arquivos";
		try {
			Upload.enviaArquivo(caminho, this);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@ManyToOne
	@PrimaryKeyJoinColumn
	public Album getAlbum() {
		return album;
	}

	public void setAlbum(Album album) {
		this.album = album;
	}

	@Override
	public String toString() {
		return getId() + " | " + arquivo;
	}
}

[color=red]Album.java[/color]

public class Album extends ValidatorForm{
	private static final long serialVersionUID = -8932529389009134248L;
	private long id;
	private String nome;
	private List<Foto> fotos;

	public Album() {
	}

	public Album(List<Foto> fotos) {
		this.fotos = fotos;
		createName();
	}

	@Id
	@GeneratedValue
	@Column(name="id")
	public long getId() {
		return id;
	}

	@Column(name = "strNome", nullable = false)
	public String getNome() {
		return nome;
	}

	public void setNome(String nome) {
		this.nome = nome;
	}

	@OneToMany (targetEntity=Foto.class, mappedBy="album", fetch=FetchType.EAGER)
	@Cascade({org.hibernate.annotations.CascadeType.ALL})
	public List<Foto> getFotos() {
		return fotos;
	}

	public void setFotos(List<Foto> fotos) {
		this.fotos = fotos;
		if (nome == null || nome.equals("")) {
			createName();
		}
	}
	
	//REMOVE FOTO
	public boolean removeFoto(Foto foto){
		return removeFoto(foto.getId());
	}	
	public boolean removeFoto(long id){
		if (fotos != null) {
			for (int i = 0; i < fotos.size(); i++) {
				if (fotos.get(i).getId() == id) {
					fotos.remove(i);
					return true;
				}
			}
		}
		return false;
	}
	// VERIFICA SE FOTO EXISTE
	public boolean isFotoExiste(Foto foto){
		return isFotoExiste(foto.getId());
	}
	public boolean isFotoExiste(long id){
		if (fotos != null) {
			for (int i = 0; i < fotos.size(); i++) {
				if (fotos.get(i).getId() == id) {
					return true;
				}
			}
		}
		return false;		
	}

	@Override
	public String toString() {
		return getId()+ " | " + nome + " | " + fotos;
	}
	
	private void createName(){
		this.nome = "album_"+new Timestamp().getDate().getTime();
	}
}

[color=red]FotoAction.java[/color]

public class FotoAction extends DispatchAction {
	private static final FotoDAO fotoDAO = FotoDAO.getInstance();

	public ActionForward deleta(ActionMapping mapping,
			ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		//DELETA BEAN PELO ID DA QUERYSTRING
		long id = Long.parseLong(request.getParameter("id"));
		if (id != 0) {
			Foto fotoDeleta = fotoDAO.get(id);
			if (fotoDeleta.getAlbum() != null && fotoDeleta.getAlbum().isFotoExiste(fotoDeleta)) {
				fotoDeleta.getAlbum().removeFoto(fotoDeleta);
				fotoDeleta.setAlbum(null);
			}
			fotoDAO.apagar(id);
		}
		return mapping.findForward(null);
	}
}

[color=red]Segue a stacktrace:[/color]

org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.es.napegada.admin.album.foto.Foto#18]
	at org.hibernate.impl.SessionImpl.forceFlush(SessionImpl.java:1230)
	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:188)
	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
	at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:685)
	at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:677)
	at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:252)
	at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
	at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:425)
	at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:362)
	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:338)
	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
	at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
	at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:154)
	at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
	at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88)
	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
	at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
	at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
	at com.es.utils.hibernate.GenericDAO.apagar(GenericDAO.java:144)
	at com.es.napegada.admin.album.foto.FotoAction.deleta(FotoAction.java:25)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.apache.struts.actions.DispatchAction.dispatchMethod(DispatchAction.java:270)
	at org.apache.struts.actions.DispatchAction.execute(DispatchAction.java:187)
	at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:425)
	at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:228)
	at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1913)
	at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:462)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:304)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.displaytag.filter.ResponseOverrideFilter.doFilter(ResponseOverrideFilter.java:125)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:929)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:405)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:279)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:515)
	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:302)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

Agradeço antecipadamente e assim que eu descobrir uma solução para a mesma posto aqui :smiley:

Pra variar descubro as coisas sozinho rsrs
levei 2 semanas por causa desse erro besta :lol:

O que ocorria era o seguinte, na minha Action:

public class FotoAction extends DispatchAction {  
    private static final FotoDAO fotoDAO = FotoDAO.getInstance();  
  
    public ActionForward deleta(ActionMapping mapping,  
            ActionForm form, HttpServletRequest request,  
            HttpServletResponse response) throws Exception {  
        //DELETA BEAN PELO ID DA QUERYSTRING  
        long id = Long.parseLong(request.getParameter("id"));  
        if (id != 0) {  
            Foto fotoDeleta = fotoDAO.get(id);  
            if (fotoDeleta.getAlbum() != null && fotoDeleta.getAlbum().isFotoExiste(fotoDeleta)) {  
                fotoDeleta.getAlbum().removeFoto(fotoDeleta);  
                fotoDeleta.setAlbum(null);  
            }  
            fotoDAO.apagar(id);  
        }  
        return mapping.findForward(null);  
    }  
} 

Tem uma grave falha de lógica ai rsrs conseguem enxergar?
eu edito o objeto beleza só que quando mando apagá-lo eu passava somente o id em vez de passar o objeto alterado, meu DAO dava um get no objeto Foto original(antes das alterações) e obviamente causava esse erro, pois ele pegou o objeto Foto antes de eu desvincular o mesmo da List do Album, então criei um método de sobrecarga no meu DAO onde posso passar tanto o id quanto o objeto diretamente, minha Action ficou assim:

[color=red]FotoAction.java[/color]

public class FotoAction extends DispatchAction {
	private static final FotoDAO fotoDAO = FotoDAO.getInstance();

	public ActionForward deleta(ActionMapping mapping,
			ActionForm form, HttpServletRequest request,
			HttpServletResponse response) throws Exception {
		//DELETA BEAN PELO ID DA QUERYSTRING
		long id = Long.parseLong(request.getParameter("id"));
		if (id != 0) {
			Foto fotoDeleta = fotoDAO.get(id);
			if (fotoDeleta.getAlbum() != null && fotoDeleta.getAlbum().isFotoExiste(fotoDeleta)) {
				fotoDeleta.getAlbum().removeFoto(fotoDeleta);
			}
			fotoDAO.apagar(fotoDeleta);
		}
		return mapping.findForward(null);
	}
}

bom só pra complementar meu FotoDAO.apagar() ficou assim(não postei ele antes mas da pra analisar pelo que expliquei logo acima):

[color=red]FotoDAO.java[/color]

//OBJETO ALTERADO PASSADO COMO PARÂMETRO
public boolean apagar(Bean bean) throws SQLException {
	boolean objSalvo = false;
	Session sessao = HibernateUtil.getSessionFactory().openSession();
	Transaction transaction = null;
	try {
		transaction = sessao.beginTransaction();
		sessao.delete(bean);
		transaction.commit();
		objSalvo = true;
	} catch (Exception e) {
		if (transaction != null) {
			transaction.rollback();
		}
		e.printStackTrace();
	} finally {
		sessao.close();
	}
	return objSalvo;
}
//OBJETO ORIGINAL PEGO PELO ID
public boolean apagar(long id) throws SQLException {
	Bean bean = null;
	bean = (Bean) get(id);
	return apagar(bean);
}

O GUJ me dá sorte, 2 semanas sem resolver tal problema e assim que postei a dúvida descobri o que estava fazendo de errado :lol:
espero que possa ajudar alguem futuramente, abraço