[RESOLVIDO] Dúvida com Java e Jpa

Estou com dúvidas quando utilizo Jpa com Java. É meu primeiro tópico aqui. :wink:

Não entendo porque dá erro quando utilizo este código:

public class testeconexao {

	public static void main(String[] args) {

		EntityManagerFactory emf = Persistence.createEntityManagerFactory("EletricaPU");
		EntityManager em = emf.createEntityManager();

		Projeto projeto = new Projeto();
		projeto.setNome("Chris");
		projeto.setAutor("projeto");
		projeto.setDescricao("teste");
		projeto.setData(DataUtil.Atual());

		em.getTransaction().begin();
		em.persist(projeto);
		em.getTransaction().commit();
		
		Projeto projeto2 = projeto.clonarSemID();

		em.getTransaction().begin();
		em.persist(projeto2);
		em.getTransaction().commit();
	}
}

Exception in thread “main” javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:86)
at teste.testeconexao.main(testeconexao.java:33)
Caused by: javax.persistence.PersistenceException: org.hibernate.HibernateException: identifier of an instance of br.aplicacao.eletrica.modelo.projeto.Projeto was altered from 1 to null


	@Override
	public Projeto clonarSemID() {
		Projeto p = copiar();
		p.setId(null);
		return this;
	}

	@Override
	public Projeto copiar() {

		Projeto p = new Projeto();

		p.setId(id);
		p.setNome(nome);
		p.setAutor(autor);
		p.setData(data);
		p.setDescricao(descricao);
		for (Fonte f : fontes) {
			p.fontes.add(f);
		}
		return this;
	}

public void salva(T obj) {
		try {
			entityManager.getTransaction().begin();
			if (obj.getId() == null) {
				entityManager.persist(obj);
			} else {
				entityManager.merge(obj);
			}
			entityManager.getTransaction().commit();
		} catch (Exception e) {
			System.out.println("NÃO SALVOU!!: ");
			e.printStackTrace();
			entityManager.getTransaction().rollback();
		}
	}

A forma como o JPA gerencia as entidades se baseia em manter uma referência a cada elemento, permitindo, então, identificar se este objeto é um novo objeto ou se é um objeto distinto.
Por alguma razão, o método clonarSemID mantém a referência do objeto em questão, apenas removendo seu ID. Coincidentemente, o ID é a PK do objeto, logo, não pode ser nula quando um objeto já está gerenciado pelo JPA.

Como faço para copiar os atributos do objeto e persistir como um novo então? Estou com uma aplicação que depende desta tarefa…

Por acaso o tipo do atributo id é um tipo primitivo?
Se for, dá uma olhada nisso

Acredito que meu mapeamento está correto.
Vou tentar outras coisas aqui.

@Entity
@Table(name = "Projeto")
@TableModel
public class Projeto implements Entidade<Projeto> {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Integer id;
	@Column(colName = "Nome", colPosition = 0)
	private String nome;
	private String autor;
	@Column(colName = "Data", colPosition = 1)
	private String data;
	private String descricao;
	@OneToMany(cascade = CascadeType.ALL)
	private List<Fonte> fontes;

	public Projeto() {
		fontes = new ArrayList<>();
	}

	public void setAutor(String autor) {
		this.autor = autor;
	}

	public void setData(String data) {
		this.data = data;
	}

	public void setDescricao(String descricao) {
		this.descricao = descricao;
	}

	public void setFontes(List<Fonte> fontes) {
		this.fontes = fontes;
		this.fontes.clear();
		this.fontes.addAll(fontes);
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getNome() {
		return nome;
	}

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

	public String getAutor() {
		return autor;
	}

	public String getData() {
		return data;
	}

	public String getDescricao() {
		return descricao;
	}

	public List<Fonte> getFontes() {
		return fontes;
	}

	@Override
	public Projeto clonarSemID() {
		Projeto p = copiar();
		p.setId(null);
		return this;
	}

	@Override
	public Projeto copiar() {

		Projeto p = new Projeto();
		p.setId(id);
		p.setNome(nome);
		p.setAutor(autor);
		p.setData(data);
		p.setDescricao(descricao);
		for (Fonte f : fontes) {
			p.fontes.add(f);
		}

		return this;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())

			return false;
		final Projeto other = (Projeto) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	@Override
	public Integer getId() {
		return id;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public String toString() {
		return nome;
	}

	@Override
	public void apagar() {

		id = null;
		nome = "";
		autor = "";
		descricao = "";
		fontes.clear();
	}
}

Resolvido.
É necessário sempre criar uma nova instância.

public class testeconexao {

	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("EletricaPU");

		EntityManager em = emf.createEntityManager();

		Projeto projeto = new Projeto();

		projeto.setNome("Chris");
		projeto.setAutor("projeto");
		projeto.setDescricao("teste");
		projeto.setData(DataUtil.Atual());

		em.getTransaction().begin();
		em.persist(projeto);
		em.getTransaction().commit();

		
		Projeto projeto2 = new Projeto();

		projeto2.setNome(projeto.getNome());
		projeto2.setAutor(projeto.getAutor());
		projeto2.setDescricao(projeto.getDescricao());
		projeto2.setData(projeto.getData());
		
		em.getTransaction().begin();
		em.persist(projeto2);
		em.getTransaction().commit();
	}
}

Isso já estava sendo feito.

Acho que o Hibernate não entende como objeto novo fazendo dessa forma. Realmente não entendo. Ele copia a referencia do objeto anterior mesmo criando um novo dessa forma.
Usando o CloneSemID(), e comparando Projeto1 e Projeto2, o equals() retorna True!!!

    @Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())

			return false;
		final Projeto other = (Projeto) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

public class testeconexao {

public static void main(String[] args) {
	EntityManagerFactory emf = Persistence.createEntityManagerFactory("EletricaPU");
	EntityManager em = emf.createEntityManager();

	Projeto projeto = new Projeto();
	projeto.setNome("Chris");
	projeto.setAutor("projeto");
	projeto.setDescricao("teste");
	projeto.setData(DataUtil.Atual());

	em.getTransaction().begin();
	em.persist(projeto);
	em.getTransaction().commit();
	
	Projeto projeto2 = projeto.clonarSemID();
	
	System.out.println(projeto2.equals(projeto)); //true

	em.getTransaction().begin();
	em.persist(projeto2);
	em.getTransaction().commit();
}

}

No método você cria um objeto p, mas retorna quem chamou o método (this), ai é a mesma referencia

Nem olhei o return.
Mas, é exatamente isso!

filha da polícia!!! Vlw

:+1:

Não, não, não!
A instrução new sempre aloca uma nova região de memória, ou seja, é um novo objeto!

O problema é esse:

@Override
public Projeto copiar() {
	Projeto p = new Projeto(); // Criou o objeto p
	p.setId(id);
	p.setNome(nome);
	p.setAutor(autor);
	p.setData(data);
	p.setDescricao(descricao);
	for (Fonte f : fontes) {
		p.fontes.add(f);
	}
	return this; // mas retornou o objeto this
}