Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Вопрос по Hibernate и Spring, нужен хотя б категоричный ответ (да/нет) 
:(
    Опции темы
PovAnd
Дата 24.3.2008, 13:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 102
Регистрация: 20.8.2007

Репутация: нет
Всего: нет



Хочу для себя окончательно определить следующую вещь, которая уже давно не дает пакоя, а ответа еще на нее я так и не нашел: как себя правильно вести, работая с Hibernate и Spring, чтобы не получать Lazy Initialization Exception.

Заранее прошу прощение за утрирование, но подругому я не пойму.
Мне известно, что  Lazy Initialization Exception возникает тогда, когда мы пытаемся получить проинициализировать объект данными из БД, обращаясь к обертке - прокси. Чтобы инициализация произошла успешно, нужна открытая session с БД. Но все держать время открытой session  не рационально и она как правило после частичной инициализации объекта закрывается.

Теперь я приведу полное описание, того как я поступаю чтобы избегать  Lazy Initialization Exception и далее расчитываю на то, что более граммотные товарищи меня поправят и укажут, где именно я не совсем прав.

Беру наиболее простой (на мой взгляд) пример:

Имеем пару объектов:
1. Человек
2. Адрес
Человек может проживать по нескольким Адресам. По одному Адресу может проживать только один Человек.

________________________________
Beans:

Код


//Person bean
public class Person {
    private Long id;
    private String name;
    private List<Adress> adresses = new ArrayList<Adress>();
    public List<Adress> getAdresses() {
        return adresses;
    }
    public void setAdresses(List<Adress> adresses) {
        this.adresses = adresses;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    } 

    public String toString(){
        return name;
    }
}

//Adress bean
public class Adress {
    private Long id;
    private String name;
    private Person person;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Person getPerson() {
        return person;
    }
    public void setPerson(Person person) {
        this.person = person;
    }

    public String toString(){
        return name;
    }
}


hibernate-mapping:

Код

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">


<hibernate-mapping>

    <class name="Person">
        <id name="id">
            <generator class="native" />
        </id>
        <property name="name" />
        <bag name="adresses" cascade="all">
            <key column="PERSON_ID"/>
            <one-to-many class="Adress" />
        </bag>
    </class>

    <class name="Adress">
        <id name="id">
            <generator class="native" />
        </id>
        <property name="name" />
        <many-to-one name="person" cascade="all" column="PERSON_ID" />
    </class>

</hibernate-mapping>



//Spring настройка доступа к DB, Hibernate и Transaction Manger:

Код

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />

        <property name="mappingResources">
            <list>
                <value>Person.xml</value>
            </list>
        </property>

        <property name="hibernateProperties">
            <props>...</props>
        </property>
    </bean>

    <bean id="txManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        ...
    </bean>
</beans>


DAO:

Код


//Person DAO 
public interface PersonDao {
    Person getPersonById(Long id);
}

//Adress DAO 
public interface AdressDao {
    List<Adress> getAdressesPerson(Person person);
    Person getAdressPerson(Adress adress);
}

// Реализация PersonDao
public class PersonDaoImpl extends HibernateDaoSupport implements PersonDao {

    public Person getPersonById(Long id) {
        List person = getHibernateTemplate().find("from Person p where p.id = ?", id);
        return person.size() == 1?(Person)person.get(0):null;
    }    

}

// Реализация AdressDao
public class AdressDaoImpl extends HibernateDaoSupport implements AdressDao {

    public List<Adress> getAdressesPerson(Person person) {
        getHibernateTemplate().refresh(person);
        getHibernateTemplate().initialize(person.getAdresses());
        return person.getAdresses();
    }
    
    public Person getAdressPerson(Adress adress){
        getHibernateTemplate().refresh(adress);
        getHibernateTemplate().initialize(adress.getPerson());
        return adress.getPerson();
    }

}



Spring конфиг для DAO:
Код

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <bean id="personDao" class="PersonDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="adressDao" class="AdressDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>


Services:

Код


//PersonService
public interface PersonService {
    Person getPersonById(Long id);
    void printAdresses(Person person);
}

//AdressService
public interface AdressService {
    List<Adress> getAdressesPerson(Person person);
    Person getAdressPerson(Adress adress);
}

//Реализация PersonService
public class PersonServiceImpl implements PersonService {
    private PersonDao personDao;
    
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }

    public Person getPersonById(Long id) {
        return personDao.getPersonById(id);
    }

    public void printAdresses(Person person) {
        System.out.println(person.getAdresses());
    }

}

//Реализация AdressService
public class AdressServiceImpl implements AdressService {
    private AdressDao adressDao;
    
    public void setAdressDao(AdressDao adressDao) {
        this.adressDao = adressDao;
    }

    public Person getAdressPerson(Adress adress) {
        return adressDao.getAdressPerson(adress);
    }

    public List<Adress> getAdressesPerson(Person person) {
        return adressDao.getAdressesPerson(person);
    }

}



Spring конфиг для Services:

Код

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
    <bean id="personService"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="txManager" />
        <property name="target">
            <bean class="PersonServiceImpl">
                <property name="personDao" ref="personDao" />
            </bean>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

    <bean id="adressService"
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="txManager" />
        <property name="target">
            <bean class="AdressServiceImpl">
                <property name="adressDao" ref="adressDao" />
            </bean>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
</beans>


____________________________________
Выше приведенный листинг, это типичный пример того, как я пишу код, используя Hibernate и Spring. Вкратце:
1. описали в рамках класса конкретные сущности, 
2. смэпили БД на эти сущности
3. написали классы, непосредственно отвечающие за изменение и выборку сущностей в БД (DAO)
4. написали классы для манипуляций с сущностями в иных случаях, не относящихся к работе с БД (Сервисы). Эти сервисы обернули в транзакшен манаджер, с тем расчетом, чтобы Spring сам смотрел, как и где надо открывать-закрывать сеансы связи с БД, разрешать-отменять комиты при работе с БД.

У меня есть сомнения, что-то, что написано это лучший вариант (ну или я чего-то не совсем правильно понимаю, используя данный подход). Опишу ситуацию, из которой такие сомнения рождаются:

Код

public class Test {
    private static final ApplicationContext context;
    static {
        String[] ctxFiles = {
                "hibernate-ctx.xml",
                "dao-ctx.xml",
                "services-ctx.xml" };
        context = new ClassPathXmlApplicationContext(ctxFiles);
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.test1();
        //test.test2();
        //test.test3();
    }

    public void test1() {
        PersonService personService = (PersonService) context
                .getBean("personService");
        Person person = personService.getPersonById(1L);
        
        System.out.println(person.getAdresses());
    }

    public void test2() {
        PersonService personService = (PersonService) context
                .getBean("personService");
        Person person = personService.getPersonById(1L);
        
        personService.printAdresses(person);
    }

    public void test3() {
        PersonService personService = (PersonService) context
                .getBean("personService");
        Person person = personService.getPersonById(1L);
        AdressService adressService = (AdressService) context
                .getBean("adressService");
        
        System.out.println(adressService.getAdressesPerson(person));
    }

}


В test3() - всё ОК, а случаях test1() и test2() получили 

Код

Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: Person.adresses, no session or session was closed
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358)
    at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350)
    at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:343)
    at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86)
    at org.hibernate.collection.PersistentBag.toString(PersistentBag.java:459)
    at java.lang.String.valueOf(Unknown Source)
    at java.io.PrintStream.println(Unknown Source)
    at com.povand.stake.parser.Test.test2(Test.java:40)
    at com.povand.stake.parser.Test.main(Test.java:23)



Вобщем какие выводы делаю из всего этого я:

В случае test1() мы получаем эксепшен, потому что объект пытаемся попросить у прокси объекта Person лист адресов, прокся пытается запросить данные у БД, но сеанс связи закрыт и поэтому облом.

Случай test2(): вывод там получается такой как и для test1(), но здесь я имею некоторые сомнения в правильности своего подхода: ведь сервис PersonService обернут транзакшен менеджером, неужели нет в Spring такой возможности, чтобы в рамках этой обертки произошла проверка: закрыта ли сессия  для  прокси данного сущности и если да , то ее нужно снова открыть?

В случае test3() все работает, но как-то криво это все получается. Ибо чтобы забрать лист Adress у объекта Person, я должен в  конечном итоге добраться аж до самого дао, чтобы объект отрефрешить и потом "ручками" проинициализировать лист адресов. И это все при том, что у объекта Person имеется метод List<Adress> getAdresses(), который как-будто издевательски имеет модификатор доступа public.
-----------------

В итоге мне было бы интересно, услышать, что именно в моих рассуждениях (исходя из  выводов) требует пересмотра точки зрения на проблему. Или как именно нужно изменить сам подход (т.е. покажите более правильный код), чтобы выводы стали другими.

В крайнем случае, исходя из моего примера: считаете ли что я правильно рассуждаю  относительно подхода использования hibernate и spring?(ДА/НЕТ)


Это сообщение отредактировал(а) PovAnd - 24.3.2008, 13:42
PM MAIL   Вверх
Kangaroo
Дата 24.3.2008, 16:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


AA - Aussie Animal
****


Профиль
Группа: Участник Клуба
Сообщений: 2042
Регистрация: 7.10.2006
Где: US

Репутация: 21
Всего: 104



Присоединяюсь к вопросу.
У нас в проекте сделано через *. Мы тянули все сразу, а там где нужно было меньше информации делали еще один маппинг. Хочется узнать как это правильно делать.




--------------------
Lost....
PM MAIL MSN   Вверх
fixxer
Дата 24.3.2008, 17:02 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 672
Регистрация: 14.9.2006
Где: Саратов, Россия

Репутация: 6
Всего: 27



Попробую высказать свое ХО. На мой взгляд тут есть два решения.

1. Open Session In View, когда сессия закрывается только после того, как view отрендерится. (В фильре, интерсепторе, еще где-то)

2. Command, когда в контекст сессии помещаются не атомарные CRUD операции из DAO, а более осмысленный законченный набор операций, так называемый Unit Of Work. А у Вас, насколько я понял, сервисный слой функционально дублирует дао слой.


--------------------
user posted image
PM MAIL ICQ   Вверх
PovAnd
Дата 24.3.2008, 17:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 102
Регистрация: 20.8.2007

Репутация: нет
Всего: нет



1. Open Session In View, когда сессия закрывается только после того, как view отрендерится. (В фильре, интерсепторе, еще где-то)

Суть ведь вопроса в том не в том, что я не знаю как обойти проблему закрытия/открытия session, а в том как это легше всего сделать в рамках использования Spring. Именно кайф в том чтобы вообще забыть о том что есть? какая-то сессия, пускай об этом думает правильно настроенный Spring. Если конечно это возможно.

2. Command, когда в контекст сессии помещаются не атомарные CRUD операции из DAO, а более осмысленный законченный набор операций, так называемый Unit Of Work. А у Вас, насколько я понял, сервисный слой функционально дублирует дао слой.

Согласно п. 2, если это возможно, покажите в рамках описанного примера, как возможно получать лист Adress для Person. 
PM MAIL   Вверх
fixxer
Дата 24.3.2008, 17:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 672
Регистрация: 14.9.2006
Где: Саратов, Россия

Репутация: 6
Всего: 27



Цитата(PovAnd @ 24.3.2008,  17:25)
2. Command, когда в контекст сессии помещаются не атомарные CRUD операции из DAO, а более осмысленный законченный набор операций, так называемый Unit Of Work. А у Вас, насколько я понял, сервисный слой функционально дублирует дао слой.

Согласно п. 2, если это возможно, покажите в рамках описанного примера, как возможно получать лист Adress для Person.

Ну вот смотрите. У вас какая задача? По заданному Person#id получить Person, а по нему список Address. Два шага.
Но у вас они разнесены по двум разным сервисам. Объедините их в одном сервисном методе, например, getPersonAddressesByPersonId. Дело в том, что по моему мнению, делить сервисы стоит не по принципу сущности, с которой работает сервис, а по принципу бизнес-задачи. Ведь смысл сервисов в том, что они добавляют дополнительный уровень косвенности.



--------------------
user posted image
PM MAIL ICQ   Вверх
PovAnd
Дата 24.3.2008, 18:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 102
Регистрация: 20.8.2007

Репутация: нет
Всего: нет



В реальности задача несколько сложнее. В данном примере согласен, наличие сервисов имеет минимальный смылс, поскольку в рамках примера нет никаких особых задач, есть только запроса Person через DAO. Но сервисы я обозначил исходя из того, что у меня есть реальная задача, где сервисы имеют больший смысл чем просто получение объекта (но проблемы описанные в примере никуда не пропадают). И уж поскольку в реальной задаче у меня имеются сервисы, то те объекты которые юзают сервисы уж точно не должны знать об объектах DAO, соответсвенно это достигается путем делегирования нужных методов DAO в сервис. 
Объедините их в одном сервисном методе, например, getPersonAddressesByPersonId.
Объединить то не проблема, не ясно только что писать в том объединении. Я хочу понять можно ли как то в рамках сервиса добиться такого(в том условно объединенном методе) кода :

List<Adress> getAdressPerson(Person person){
    return person.getAdress();
}

вместо  без кайфового такого:

...
    getHibernateTemplate().refresh(person);    
    getHibernateTemplate().initialize(person.getAdresses());
.... 

Вот если мне станет ясно, как сделать рабочим(без возбуждения LazyException..) такой метод исходя из условия, что объект person может придти откуда угодно, то это будет шИкарно.

В моем понимании, обернутый в проксю бин вот с этим методом, работать должне так:

у интерфейса сервиса вызывается метод List<Adress> getAdressPerson(Person person) . На самом деле реализация этого метода - прокся с делегатом в роле которого родная имплементациия моего сервиса. Так вот эта прокся, перед вызовом  метода getAdressPerson() у моей иплементации должна проверить сессию для объекта person и затем и если нужно открыть ,  иопять же, если нужно - проинициализировать объект, и за тем уже передать объект методу в моей имплементации где без всякого напряга для меня вызавется без траблов person.getAdress().. весь вопрос в том есть ли такая волщебная прокся и если есть - как ее юзать.


Это сообщение отредактировал(а) PovAnd - 24.3.2008, 18:15
PM MAIL   Вверх
mindflyer
Дата 25.3.2008, 13:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 113
Регистрация: 20.10.2004
Где: Smolensk, Russia

Репутация: 1
Всего: 4



Вообще, узнать открыта/закрыта ли сессия и проинициализирован ли объект/коллекция можно через хибернейтовые интерфейсы: org.hibernate.proxy.HibernateProxy для объекта и org.hibernate.collection.AbstractPersistentCollection для коллекции.

Цитата(PovAnd @  24.3.2008,  18:14 Найти цитируемый пост)
Вот если мне станет ясно, как сделать рабочим(без возбуждения LazyException..) такой метод исходя из условия, что объект person может придти откуда угодно, то это будет шИкарно.

Со спрингом никогда не работал и не могу посоветовать как делать в его случае, но в случае EJB SessionBean имел сходную проблему, которую можно решить навесив особый интерсептор на SB. Этот интерсептор будет смотреть проинициализирован ли объект/коллекция - если нет, то он может подменять объект на объект из бд в рамках текущей сессии/транзакции и в сам SB уйдёт уже новый объект, который может корректно проинициализироваться. А в коде метода тогда будет именно что простейший код типа "return person.getAdress();". Однако, это решение именно что для простейших ситуаций.

А если подходить более абстрактно, то присоединяюсь к:
Цитата(fixxer @  24.3.2008,  17:02 Найти цитируемый пост)
2. Command, когда в контекст сессии помещаются не атомарные CRUD операции из DAO, а более осмысленный законченный набор операций, так называемый Unit Of Work. 

PM MAIL ICQ   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1029 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.