Strona główna > Java > Opakowanie wynik zapytani JPA do DTO – jeszcze raz

Opakowanie wynik zapytani JPA do DTO – jeszcze raz

W ostatnim wpisie, prawiłem jak to dobrze jest w niektórych przypadkach napisać trochę więcej kod, aby nie gmatwać się później. Tą większą ilością kodu było tworzenie nowej klasy typu DTO, do użycia w zapytaniach JPA. Gmatwaniną był bałagan powstający ze zbyt uporczywej re-używalności encji jako DTO.
Niedługo po tym zdarzył mi się przypadek, który wymagał zastosowania się do tej zasady (przy okazji wyszło że łatwo jest gadać jakie to „ma być”, a jak już ma się to zastosować, to wychodzi że nie jest to takie oczywiste i bezbolesne ;)).
Przechodząc do setna: realizacja tego dla przykładowych encji:

@Entity
@Table(name = "PARENT")
public class Parent {

	@Id
	@GeneratedValue
	@Column(name = "ID")
	private Long id;
	
	@Column(name = "NAME")
	private String name;
	
    @OneToOne(fetch=FetchType.LAZY, cascade={CascadeType.ALL})
    @JoinColumn(name="CHILDREN_ID")
	private Children children;
	
	// getters, setters
}
...
@Entity
@Table(name = "CHILDREN")
public class Children {

	@Id
	@GeneratedValue
	@Column(name = "ID")
	private Long id;
	
	@Column(name = "NAME")
	private String name;

	// getters, setters
}

sprowadza się to do takiego kodu:

		List<Object[]> tempList = entityManager
		.createQuery("select d.name, d.children.name from Parent d")
		.getResultList();
		List<Parent> returnList = null;
		if (tempList == null) {
			// to jest trochę defensywne zachowanie, 
			// niby specyfikacja nie wspomina o tym że może być null
			returnList = Collections.emptyList();
		} else {
			returnList = new ArrayList<Parent>();
			for (Object[] record : tempList) {
				Parent parent = new Parent();
				parent.setName((String)record[0]);
				parent.setChildren(new Children());
				parent.getChildren().setName((String)record[1]);
				returnList.add(parent);
			}
		}

Tutaj dla uproszczenia nie wprowadzałem już osobnego DTO. Widać tu jednak jak można konstruować zapytania tak aby pobierać to co się chce i żeby było to umieszczane w odpowiednich miejscach.
Jest oczywiście prostsza metoda, z delegacją cześć zadania do konstruktora, przykład:

		List<Parent> returnList = entityManager
		.createQuery("select new Parent(d.name, d.children.name) from Parent d")
		.getResultList();

Jednak w tym przypadku rozdzielamy informacje o tym „co” „gdzie” ma być umieszczone. To też było kłopotem dla mnie, opisywałem to dawniej; przedstawiałem tam także zarys sposobu jak chciałem to usprawnić. Jednak z czasem zebrana wiedza uleżała się i dzięki sposobowi który opisywałem jeszcze w innym poście wiem jak to zrobić. Co też opisze dalej.

Tak więc za ostatnim przytaczanym tu wpisem korzystam z rozdzielenia tego co ma być używane od tego jak to ma być używane. Co w specyfikacji wygląda tak:

	private static class ParentQueryDTOBuilder extends JPAQueryBuilderBase<Parent> {
		// co
		public static final SelectValue<String> name = 
			new SelectValue<String>("d.name");
		public static final SelectValue<String> childrenName = 
			new SelectValue<String>("d.children.name");
		// jak
		protected Parent create() {
			Parent parent = new Parent();
			parent.setName(value(name));
			parent.setChildren(new Children());
			parent.getChildren().setName(value(childrenName));
			return parent;
		}

	}

A w przytaczanych przykładach prezentuje się tak:

ParentQueryDTOBuilder builder = new ParentQueryDTOBuilder();
Query query = entityManager
.createQuery("select "+builder.createQueryPartString(
  ParentQueryDTOBuilder.childrenName, 
  ParentQueryDTOBuilder.name
  )+" from Parent d");
List<Parent> listReturn = builder.repackeResultList(query.getResultList());

Kodu JPAQueryBuilderBase został umieszczony na samym końcu.
Porównując to do tego z czego wychodziliśmy, całość końcowego efektu może nie powala🙂. Jednak zysk tutaj będzie jeśli nasze DTO jest inicjowane na podstawie 10 argumentów. Wtedy to pomyłka w przestawieniu zmiennych będzie eliminowana.
Można jeszcze pokusić się o dodatkowe usprawnienia, np. dać możliwość definiowania takiego czegoś:

		public static final SelectValue<String> childrenName = 
			new SelectValue<String>(root("d").getChildren().getName());
public abstract class JPAQueryBuilderBase<E> {

	public List<E> repackeResultList(List<Object[]> list){
		List<E> listReturn = null;
		if (list == null) {
			listReturn = Collections.emptyList();
		} else {
			listReturn = new ArrayList<E>(list.size());
			for (Object record : list) {
				// tutaj drobne zabezpieczenie jeśli
				// zapytanie było po jednej kolumnie
				if (! record.getClass().isArray()){
					listReturn.add(assemble(new Object[]{record}));
				} else {
					listReturn.add(assemble((Object[])record));
				}
			}
		}
		return listReturn;
	}
	
	// swiadomosc przerabianego rekordu
	// wprowadzenie do stanu aby lepiej 
	// prezentowało się przy definicji,
	// chociaż będzie to gorsze dla współdzielenia
	private Object[] record;
	
	private E assemble(Object[] record) {
		this.record = record; 
		return (E) create();
	}
	
	// uzywane w definicji do wskazania wartości
	@SuppressWarnings("unchecked")
	protected <F> F value(SelectValue<F> key) {
		Integer integer = selectValueMap.get(key);
		if (integer == null) {
			// tutaj jeśli potrzeba mozna zmodyfikować tak aby 
			// inna domyślan wartość była zwarcana
			return null; 
		}
		return (F)this.record[integer];
	}

	// to dla opisu jak ma być obiekt konstruowany
	protected abstract E create();

	private final HashMap<SelectValue<?>, Integer> selectValueMap = 
	  new HashMap<JPAQueryBuilderBase.SelectValue<?>, Integer>();

	// produkcja części stringa do umieszczenia w zapytaniu JPA
	public String createQueryPartString(SelectValue<?> ... selectValues) {
		String queryPartString = "";
		int index = 0;
		
		for (SelectValue<?> selectValue : selectValues) {
			queryPartString = queryPartString + selectValue.path + " ,";
			selectValueMap.put(selectValue, index++);
		}
		queryPartString = queryPartString.substring(0, (queryPartString.length() - 1));
		return queryPartString;
	}

	protected static class SelectValue<E> {
		private final String path;
		public SelectValue(String path) {
			this.path = path;
		}
	}
}
Kategorie:Java Tags: , ,
  1. Brak komentarzy.
  1. No trackbacks yet.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

%d bloggers like this: