Здравствуйте уважаемые форумчане.
В 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
вообщем я запутался в конец.
помогите с этой кашей разобраться пжлст ))