Работа с колекции от мързеливи елементи в JPA

Java Top

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА

1. Общ преглед

Спецификацията JPA предоставя две различни стратегии за извличане: нетърпелив и мързелив. Докато мързеливият подход помага да се избегне ненужно зареждане на данни, от които нямаме нужда, понякога трябва да прочетем данни, които първоначално не са били заредени в затворен контекст на постоянство. Освен това достъпът до колекции от мързеливи елементи в затворен контекст на постоянство е често срещан проблем.

В този урок ще се съсредоточим върху това как да заредим данни от колекции от мързеливи елементи. Ще проучим три различни решения: едно, включващо езика на заявките JPA, друго с използване на графики на обекти и последното с разпространение на транзакции.

2. Проблемът с колекцията на елементи

По подразбиране JPA използва стратегията за мързеливо извличане в асоциации от тип @ElementCollection . По този начин всеки достъп до колекцията в затворен контекст на постоянство ще доведе до изключение.

За да разберем проблема, нека дефинираме модел на домейн въз основа на връзката между служителя и неговия телефонен списък:

@Entity public class Employee { @Id private int id; private String name; @ElementCollection @CollectionTable(name = "employee_phone", joinColumns = @JoinColumn(name = "employee_id")) private List phones; // standard constructors, getters, and setters } @Embeddable public class Phone { private String type; private String areaCode; private String number; // standard constructors, getters, and setters }

Нашият модел уточнява, че служителят може да има много телефони. Списъкът с телефони е колекция от вграждаеми типове . Нека използваме пролетното хранилище с този модел:

@Repository public class EmployeeRepository { public Employee findById(int id) { return em.find(Employee.class, id); } // additional properties and auxiliary methods } 

Сега, нека възпроизведем проблема с прост JUnit тест:

public class ElementCollectionIntegrationTest { @Before public void init() { Employee employee = new Employee(1, "Fred"); employee.setPhones( Arrays.asList(new Phone("work", "+55", "99999-9999"), new Phone("home", "+55", "98888-8888"))); employeeRepository.save(employee); } @After public void clean() { employeeRepository.remove(1); } @Test(expected = org.hibernate.LazyInitializationException.class) public void whenAccessLazyCollection_thenThrowLazyInitializationException() { Employee employee = employeeRepository.findById(1); assertThat(employee.getPhones().size(), is(2)); } } 

Този тест хвърля изключение, когато се опитваме да осъществим достъп до телефонния списък, защото контекстът на постоянство е затворен .

Можем да разрешим този проблем, като променим стратегията за извличане на @ElementCollection, за да използваме нетърпеливия подход . Извличането на данни с нетърпение обаче не е непременно най-доброто решение , тъй като телефонните данни винаги ще бъдат заредени, независимо дали имаме нужда от тях.

3. Зареждане на данни с език за заявки JPA

Езикът за заявки JPA ни позволява да персонализираме прогнозираната информация. Следователно можем да дефинираме нов метод в нашия EmployeeRepository за избор на служител и неговите телефони:

public Employee findByJPQL(int id) { return em.createQuery("SELECT u FROM Employee AS u JOIN FETCH u.phones WHERE u.id=:id", Employee.class) .setParameter("id", id).getSingleResult(); } 

Горната заявка използва операция за вътрешно присъединяване, за да извлече телефонния списък за всеки върнат служител.

4. Зареждане на данни с графика на обекта

Друго възможно решение е да използвате функцията за графика на обекти от JPA. Графиката на обекта ни дава възможност да изберем кои полета ще бъдат проектирани чрез JPA заявки. Нека дефинираме още един метод в нашето хранилище:

public Employee findByEntityGraph(int id) { EntityGraph entityGraph = em.createEntityGraph(Employee.class); entityGraph.addAttributeNodes("name", "phones"); Map properties = new HashMap(); properties.put("javax.persistence.fetchgraph", entityGraph); return em.find(Employee.class, id, properties); } 

Виждаме, че нашата графика на обекта включва два атрибута: име и телефони . Така че, когато JPA превежда това на SQL, ще проектира свързаните колони.

5. Зареждане на данни в транзакционен обхват

Накрая ще проучим едно последно решение. Досега видяхме, че проблемът е свързан с жизнения цикъл на контекста на устойчивост.

Това, което се случва, е, че нашият контекст на постоянство е обхванат от транзакцията и ще остане отворен, докато транзакцията приключи . Жизненият цикъл на транзакцията обхваща от началото до края на изпълнението на метода на хранилището.

И така, нека да създадем друг тестов случай и да конфигурираме нашия контекст на постоянство да се свързва с транзакция, стартирана от нашия тестов метод. Ще държим контекста на устойчивост отворен, докато тестът приключи:

@Test @Transactional public void whenUseTransaction_thenFetchResult() { Employee employee = employeeRepository.findById(1); assertThat(employee.getPhones().size(), is(2)); } 

The @Transactional annotation configures a transactional proxy around the instance of the related test class. Moreover, the transaction is associated with the thread executing it. Considering the default transaction propagation setting, every Persistence Context created from this method joins to this same transaction. Consequently, the transaction persistence context is bound to the transaction scope of the test method.

6. Conclusion

In this tutorial, we evaluated three different solutions to address the problem of reading data from lazy associations in a closed Persistence Context.

First, we used the JPA query language to fetch the element collections. Next, we defined an entity graph to retrieve the necessary data.

И в крайното решение използвахме Spring Transaction, за да поддържаме контекста на устойчивост отворен и четем необходимите данни.

Както винаги, примерният код за този урок е достъпен в GitHub.

Дъно на Java

Току що обявих новия курс Learn Spring , фокусиран върху основите на Spring 5 и Spring Boot 2:

>> ПРЕГЛЕД НА КУРСА