Spring+Hibernate + OneToMany<->ManyToOne bidirectional relationship
От: andreyzz  
Дата: 24.10.13 18:25
Оценка:
Здравствуйте уважаемые форумчане.

В hibernate orm я новичок, потому запутался в 3х соснах. гугль особо не помог,видимо не те вопросы ему задаю.
потому расчитываю на помощь живых людей.
итак ситуация
вот куски кода( набирал руками, чтобы отразить основные моменты, которые я не понимаю)

 public class Parent  {
 ...
 @Access(value = AccessType.PROPERTY)
  @ElementCollection(targetClass = ParentData.class)
  @Cascade({org.hibernate.annotations.CascadeType.ALL})
  @OneToMany(mappedBy = "parent",/*cascade = CascadeType.ALL,*/ fetch = FetchType.EAGER, orphanRemoval = true)
  private Set<ParentData> parentDataList;
  
  @Transient
  private Actual3rdPartyParentDataHolder dataHolder;
  
  ...
  Set<ParentData> getParentDataList() {
    
    for (ParentData c : dataHolder.getData()) {
      parentDataList.add(ParentData.createData(this, c));
    }
    return parentDataList;
  }
  public void setAccountCookieList(Set<ParentData> parentDataList) {
    assert this.parentDataList != null;
    this.parentDataList.addAll(parentDataList);
    dataHolder.addAll(parentDataList)
  }
  }
  
  class PArentData imlements IData {
    ....
    тут Simple java types  (int, string,boolean etc)
    
  }
  class Actual3rdPartyParentDataHolder {
    public IData getData() {
        return врутренняя реализация этого интерфейса, представленная 3rd party developer.
    }
  }
  
  public class ParentData implements IData {
     
     ...
     @ManyToOne(fetch = FetchType.EAGER) // вот тут у меня есть сомнения, м.б. надо LAZY?
    @Cascade({org.hibernate.annotations.CascadeType.ALL})
    @JoinColumn(name = "parent_id")
     private Parent parent; //геттер и сеттер обычные автогенереные, без изысков
     
     public static ParentData createData(Parent parent,IData data) {
        setParent(parent).
        ...
        //копируем данные из data в свои поля
     }
     }


@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"off.register.persistence"}, excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Configuration.class))
public class PersistenceConfiguration {

  @Autowired
  @Qualifier("sessionFactory")
  private SessionFactory sessionFactory;

  @Bean
  public PlatformTransactionManager transactionManager() {
    return new HibernateTransactionManager(sessionFactory);
  }

  @Bean
  public HibernateExceptionTranslator hibernateExceptionTranslator() {
    return new HibernateExceptionTranslator();
  }

  @Configuration
  @Profile("dev")
  @PropertySource("classpath:persistence.properties")
  static class Develop {
    @Bean
    @Qualifier("sessionFactory")
    public LocalSessionFactoryBean sessionFactory(Environment environment) {
      String packageOfModelBeans = Account.class.getPackage().getName();

      LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();

      factoryBean.setDataSource(dataSource());
      factoryBean.setHibernateProperties(buildHibernateProperties(environment));
      factoryBean.setPackagesToScan(packageOfModelBeans);
      return factoryBean;
    }

    @Bean
    public DataSource dataSource() {
      return new EmbeddedDatabaseBuilder()
              .setType(EmbeddedDatabaseType.HSQL)
                      //.addScript("classpath:com/bank/config/sql/schema.sql")
                      //.addScript("classpath:com/bank/config/sql/test-data.sql")
              .build();
    }

    /**
     * Loading all the hibernate properties from a properties file
     */
    protected Properties buildHibernateProperties(Environment env) {
      Properties hibernateProperties = new Properties();

      hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
      hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
      hibernateProperties.setProperty("hibernate.use_sql_comments", env.getProperty("hibernate.use_sql_comments"));
      hibernateProperties.setProperty("hibernate.format_sql", env.getProperty("hibernate.format_sql"));
      hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));

      hibernateProperties.setProperty("hibernate.generate_statistics", env.getProperty("hibernate.generate_statistics"));

      hibernateProperties.setProperty("javax.persistence.validation.mode", env.getProperty("javax.persistence.validation.mode"));

      //Audit History flags
      hibernateProperties.setProperty("org.hibernate.envers.store_data_at_delete", env.getProperty("org.hibernate.envers.store_data_at_delete"));
      hibernateProperties.setProperty("org.hibernate.envers.global_with_modified_flag", env.getProperty("org.hibernate.envers.global_with_modified_flag"));

      return hibernateProperties;
    }


  }

}




interface ParentDao {
  Parent find(Integer Id);
}

@Repository
public class ParentDaoImpl {

  @Autowired
  ServiceFactory sf;
  public Parent find(Integer id) {
    return (Parent) sf.getCurrentSession().get(Parent.class,id);
  }
}

interface ParentService {
 Parent find(Integer Id);
}
@Service
public class PArentServiceImpl {
  @Autowired
  ParentDao dao;
  public Parent find(Integer id) {
   return dao.find(id);
  }
  
}


теперь я пытаюсь все это завести.
делаю тест

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("dev")
@ContextConfiguration(
        loader = AnnotationConfigContextLoader.class,
        classes = {
                PersistenceCOnfiguration.class
        })
@Transactional
public class ParentTest {

@Autowired
  ParentService parentService;
  private  Parent p ;
  @Before
  public void setup() {
      p = new Parent();
      p.getDataHolder().populateWithData(...)// вообщем заполняем поля которые Set<? extends IData>
      parentService.save(p);
      assertNotNull(p.getId()); // будет null, если не сохранился
  }
  
  @Test 
  public void test_can_find() {
      Parent p2 =  parentService.find(p.getId());
      assertNotSame(p2,p);
  }
}


и вот тут в test_can_find у меня начинаются танцы с бубном ибо
assertNotSame(p2,p) — неверно, ну это я и так знаю ибо ParentTest объявлен как @Transactional

если я убираю @Transactional — тест падает на NullPointerException в Parent#setParentDataList() при поптыке прочитать значение

насколько я понял, проблема заключается в том, что hibernate в качестве значений для parentDataList в классе PArent подсовывает свои прокси классы, которые в свою очередь имеют lazy-initialization полей класса ParentData ( почему? у меня ведь в @OneToMany и @ManyToOne объявлены оба как FetchType.EAGER)

внимание вопрос
как на выходе своего parentService получить цельный экзепляр Parent с востановленным dataHolder внутри него
при этом, чтобы можно было обращаться к этому экземпляру вне методов, объявленных как @Transactional и повторные запросы parentService.find(id) возвращали мне не один и тот же (прокси-)класс ( или экземпляр PArent который ссылается на (прокси?)класс для Set<ParentData>)?
т.е. я хочу при загрузке из parentService экземпляра
Parent прозрачно проинициализировать некоторые значения поля @Transient
private Actual3rdPartyParentDataHolder dataHolder;
и получить этот Parent никоим образом более не относящийся к hibernate, т.е. хочу свой POJO назад
читал что-то про interface UserType и interface CompositeUserType — ничего не понял и отложил это на потом — оно мне подойдет или токо время в моем случае зря потрачу?

и правильно ли я понимаю, что аннотация @Transactional эквивалентна следующему псевдо-коду:
@Transactional public void method();
=
beginTransaction();
boolean r=false;
try {
 method();
 r=true;
catch(Exception) {
} finally {
 if (r)
  commit();
 else 
   rollback()
}



целый день убил на поиски решения, все что я смог придумать это для parentDAO методов типа save, findById
добавить что-то типа

public void save(Parent p) {
  p.copyDataFrom3rdPartyToParentDataSet()
  serviceFactory().getCurrentSession().save(p);
}
public PArent load(Integer id) {
  Parent p = (Parent )serviceFactory().getCurrentSession().load(Parent.class,id);    
  p.copyDataFRomParentDataSetTo3rdPartyDataHolder();
}

и соотв-но сделать обычные геттер\сеттер методы для Parent#parentDataList, не забыв при этом поменять аннотацию
@Access(value = AccessType.PROPERTY) на @Access(value = AccessType.FIELD)
для
Set<ParentData> parentDataList в классе PArent

но это пока токо в теории, на практике не проверял

была мысль о том, чтобы метод find в parentService сделать без @Transactional аннотации , а самому открывать и закрывать сессию
но в этом случае у меня вылетает ошибка в тесте на parentService.find(id)
failed to lazily initialize — no session or session was closed — гугль подсказывает, что дело в FetchType.LAZY для OneToMany/ManyToOne
но у меня-то там изначально стоит EAGER

вообщем я запутался в конец.

помогите с этой кашей разобраться пжлст ))
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.