lunes, 3 de agosto de 2020

La API de JPA 2.1 (VII) Copiar entidades de una base de datos a otra

1. Introducción 

Este problema parece sencillo pero no lo es, sobre todo cuando una entidad tiene colecciones y además los ID son generados automáticamente:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)

2. Proceso

  
  1. Ejecutar una consulta y obtener una lista (colección) de objetos de la entidad a copiar
  2. Para cada instancia:
    2.1 Se crea un objeto igual al de la instancia
    2.1 Mediante recursividad, se copian todos todos los campos del objeto y se asigna NULL al Id propio y de cada una de las colecciones que tenga en el objeto copia.
    2.3 Hacer un persist del nuevo objeto en la nueva EntityManager (EntityManager.persist(obj))

Al recorrer todos los campos del objeto y los campos de los objetos anidados conseguimos que se haya cargado completamente el objeto (pues el "Lazy Loading", solo carga lo que se necesita. 

El objeto original NO DEBE ESTAR DETACHED, pues no haria el "lazy loading" mientras el objeto no està "detached"), 

Para cada instancia (que llamamos "obj") se hará:

1
2
3
T copy=obj; //Define a copy object
ReflectionUtilsEdu.setSameValueToAllNestedBaseFields(copy,"id",null); //Copy by reflection to force lazy Loading
em.persist(copy); //Save the copy

3. Función para hacer nulls los Ids

En esta función le pasamos de parámetros:
  1. El objeto a modificar el atributo recursivamente
  2. Nombre del atributo
  3. Valor del atributo
Si el nombre del atributo le indicamos "id" y al valor "null" entonces hay que tener en cuenta que cualquier campo que se llame id se va a hacer nulo, indepenedienntemente que esté en dicho objeto o en cualquiera de sus colecciones!!!

Aqui le pasamos el objeto y el nombre del campo y valor
Este és el código:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public static Object ObjectCloneAndsetSameValueToAllNestedBaseFields(Object obj,String fldName,Object value) throws Exception {
		
	if (obj==null) return null;
		
	//Create the clone object
	Object clone=obj.getClass().getDeclaredConstructor().newInstance();

	//Get all fields
	List<Field> lFlds= new ArrayList<>(Arrays.asList(FieldUtils.getAllFields(obj.getClass())));
		
	for (Field fld:lFlds) {
			
		// 1. The field value has to be changed
		if (fld.getName().equals(fldName)) FieldUtils.writeField(fld, clone, value, true);
			
		// 2. The field  is a descendant of Base
		else if (Base.class.isAssignableFrom(fld.getType())) {
			Object myObjA=FieldUtils.readField(fld, obj, true);
			Object myObjB=ObjectCloneAndsetSameValueToAllNestedBaseFields(myObjA,fldName, value);
			FieldUtils.writeField(fld, clone, myObjB, true);
			
		// 3. the field is a Collection: let's copy the collection
		}else if (Collection.class.isAssignableFrom(fld.getType())) {
			Collection<?> collA=(Collection<?>)FieldUtils.readField(fld, obj, true);
			// Presume collections as Lists !!!!!!!! May fail if Sets or other Collections type !!!!!!
			Collection<Object> collB=new ArrayList<Object>(); 
			if (collA!=null) {
				Iterator<?> iter=collA.iterator();
				Object myObjA=iter.next();
				Object myObjB=ObjectCloneAndsetSameValueToAllNestedBaseFields(myObjA,fldName, value);
				collB.add(myObjB);
			}
			FieldUtils.writeField(fld, clone, collB, true);
			
		// 4. If the class is serializable, let's use SerializationUtils from Apache	
		}else if (Serializable.class.isAssignableFrom(fld.getType())) {
			Serializable myObjA=(Serializable) FieldUtils.readField(fld, obj, true);
			Serializable myObjB=SerializationUtils.clone(myObjA);
			FieldUtils.writeField(fld, clone, myObjA, true);
		
		// 5. A primitive type or not serializable is assigned without cloning	
		}else if (ClassUtils.isPrimitiveOrWrapper(fld.getType()) 
			  || !Serializable.class.isAssignableFrom(fld.getType())){
			Object myObjA=FieldUtils.readField(fld, obj, true);
			FieldUtils.writeField(fld, clone, myObjA, true);
			
		// 5. Throw exception for other fields
		} else {
			throw new Exception("EDU: The object " + fld.getName() + "(" +fld.getType() + ") is not clonable");
		}
	}
	return clone;
}

4. Posibles errores

1. Verificar que exista un esquema general (en posgres "public") que pueda albergar las relaciones que no entran en un esquema particular. Esto me pasó por borrar el esquema "public" en Postgres.
2. Hibernate hace un proxy del objeto, lo que significa que altera su estructura. Las colecciones las mete en una especie de saco, y és difícil averiguar si son Lists, Sets o otra estructura. En principio se ha supuesto aue son Lists, por tanto si falla , hay que rehacer el código para que acepte otra clase de estructura para las colecciones.


No hay comentarios :

Publicar un comentario