drop table if exists employee; drop table if exists departments; CREATE TABLE `department` ( `department_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `caption` varchar(255) DEFAULT NULL) ENGINE=InnoDB; CREATE TABLE `employee` ( `employee_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `fio` varchar(255) DEFAULT NULL, `fk_department_id` int(11) NOT NULL, FOREIGN KEY (`fk_department_id`) REFERENCES `department` (`department_id`) ) ENGINE=InnoDB ; mysql> insert into department values (NULL, 'managers'); Query OK, 1 row affected (0.03 sec) mysql> select * from department; +---------------+----------+ | department_id | caption | +---------------+----------+ | 1 | managers | +---------------+----------+ 1 row in set (0.02 sec) mysql> insert into employee values (NULL, 'Jim', 1); Query OK, 1 row affected (0.02 sec) mysql> select * from employee; +-------------+------+------------------+ | employee_id | fio | fk_department_id | +-------------+------+------------------+ | 1 | Jim | 1 | +-------------+------+------------------+ 1 row in set (0.00 sec) mysql> insert into employee values (NULL, 'Tom', 2); ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`testmach/employee`, CONSTRAINT `e mployee_ibfk_1` FOREIGN KEY (`fk_department_id`) REFERENCES `department` (`department_id`))
| « Системы управления версиями для программистов и не только. Часть 4 | Hibernate: Set-ы, bag-и и все, все, все » |
Hibernate: каскадные обновления, инверсия отношений и прочая и прочая
Вот пришло время и мне написать пару строчек про hibernate. Я попробую сделать небольшой cheatsheet по вопросу двусторонней ассоциации, каскадных обновлений, ленивой загрузки и прочего и прочего. Сразу предупрежу, что я довольно негативно отношусь к hibernate, предпочитаю в практике использовать ibatis. Может, причиной является мой опыт в проектировании БД, и я всегда предпочитаю идти именно от базы к модели классов java, а не наоборот. Большинство проблем, которые возникают у новичков заключается в том, что они забывают, что база данных живет по другим правилам, чем слой объектов. В СУБД нет всех этих двусторонних связей, да и в понятие каскадных обновлений вкладывается немного другой смысл. Естественно, что я не исключаю ситуации, “что фокусник был пьян и фокус не удался”, так что ваши замечания будут для меня полезны. Несмотря на то, что я излагаю пример на базе mysql, я полагаю, что основные идеи и выводы будут применимы для любой СУБД. Одним словом, поехали:Для примера есть такая модель данных из двух таблиц: Сотрудники и Отделы. Отношения между ними в терминах СУБД “один-ко-многим”, где на стороне “один” находится Отдел, а на стороне “много” находится “Сотрудники”. В каждую из табличек я добавлю первичный ключ - id, название отдела/ФИО сотрудника, остальные поля не существенны. Сразу же нужно определиться с тем, будут ли наши сотрудники существовать вне отделов (и это решение очень, очень важное). Предположим, что такое не возможно: сотрудник без отдела тут же удаляется, обратная же ситуация вполне возможна: отдел может существовать без сотрудников сколь угодно долго. Для реализации связи между этим двумя таблицами я должен в таблицу “сотрудники” добавить внешний ключ “fk_department_id”. Надо сказать, что когда я создаю внешний ключ с помощью sql, то могу/скорее должен указать модификаторы этого внешнего ключа. Например, для mysql, этот код будет выглядеть так:

Так я создаю таблицу “отделы”:
CREATE TABLE `department` (
`department_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`caption` varchar(255) DEFAULT NULL) ENGINE=InnoDB;
Теперь код создающий таблицу “сотрудники”:
CREATE TABLE `employee` (
`employee_id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`fio` varchar(255) DEFAULT NULL,
`fk_department_id` int(11) NOT NULL,
FOREIGN KEY (`fk_department_id`) REFERENCES `department` (`department_id`) БЛА-БЛА-БЛА
) ENGINE=InnoDB ;
CASCADE. Каскадные обновления. В коде sql записываются так:
FOREIGN KEY (`fk_department_id`) REFERENCES `department` (`department_id`) ON DELETE cascade ON UPDATE cascade
SET NULL. При изменении главной записи в подчиненной таблице значение поля fk_department_id для всех затронутых изменением отдела сотрудников станет равным null. Соотвественно, такой режим возможен лишь тогда, когда поле fk_department_id было объявлено с модификатором NULL. Явно не наш случай.
NO ACTION. Запрет на выполнение операции. Фактически если я хочу удалить или перенумеровать отдел, то выполнить это не возможно до тех пор, пока у отдела есть сотрудники. Что же довольно логично: перед расформированием отдела нужно предварительно разобраться с его сотрудниками, например, удалить или перевести в другой отдел.
Такое же поведение, как и NO ACTION вызывает RESTRICT. Точно такое же поведение происходит и тогда, когда я не указываю явно какой-либо из модификаторов.
Последний модификатор: SET DEFAULT. Фактического значения не имеет т.к. mysql игнорирует данное выражение.
Теперь приведем код java классов: отдел и сотрудник.
package experimental.business;
import java.util.Set;
/*** Отдел*/public class Department {
Integer department_id;
String caption;
Set<Employee> employies = new HashSet<Employee>();
public Department() {
}public Department(String caption) {
this.caption = caption;
}public Integer getDepartment_id() {
return department_id;
}public void setDepartment_id(Integer department_id) {
this.department_id = department_id;
}public String getCaption() {
return caption;
}public void setCaption(String caption) {
this.caption = caption;
}public Set<Employee> getEmployies() {
return employies;
}public void setEmployies(Set<Employee> employies) {
this.employies = employies;
}}
package experimental.business;
/*** Сотрудник*/public class Employee {
Integer employee_id;
String fio;
Department department;public Employee() {
}public Employee(String fio) {
this.fio = fio;
}public Integer getEmployee_id() {
return employee_id;
}public void setEmployee_id(Integer employee_id) {
this.employee_id = employee_id;
}public String getFio() {
return fio;
}public void setFio(String fio) {
this.fio = fio;
}public Department getDepartment() {
return department;
}public void setDepartment(Department department) {
this.department = department;
}}
Теперь переходим к написанию правил отображений “реляция-классы-и-обратно”:
Сначала отображение для отдела:
<!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping package="experimental.business"><class name="Department"><id name="department_id"><generator class="native" /></id><property name="caption" /><set name="employies"><!-- для организации связи между таблицами, нужно поместитьв класс зависящий от главной таблицы внешний ключ --><key column="fk_department_id" /><one-to-many class="Employee" /></set></class></hibernate-mapping>
<!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping package="experimental.business"><class name="Employee"><id name="employee_id"><generator class="native"/></id><property name="fio"/><!--Здесь также нужно указать, что связь между сотрудником и отделом обспечивается за счетполя fk_department_id помещаемого именно в эту таблицу (сотрудника)Ну а модификатор not-null="true" говорит, что сотрудники не могут существовать вне отдела--><many-to-one name="department" class="Department" column="fk_department_id" not-null="true"/></class></hibernate-mapping>
CREATE TABLE `employee` (
`employee_id` int(11) NOT NULL AUTO_INCREMENT,
`fio` varchar(255) DEFAULT NULL,
`department` int(11) DEFAULT NULL,
`id` int(11) DEFAULT NULL,
PRIMARY KEY (`employee_id`),
KEY `FK4AFD4ACEE21047AC` (`department`),
KEY `FK4AFD4ACEAF821175` (`id`),
CONSTRAINT `FK4AFD4ACEAF821175` FOREIGN KEY (`id`) REFERENCES `department` (`department_id`),
CONSTRAINT `FK4AFD4ACEE21047AC` FOREIGN KEY (`department`) REFERENCES `department` (`department_id`)
) ENGINE=InnoDB;
CREATE TABLE `employee` (
`employee_id` int(11) NOT NULL AUTO_INCREMENT,
`fio` varchar(255) DEFAULT NULL,
`fk_department_id` int(11) DEFAULT NOT NULL,
PRIMARY KEY (`employee_id`),
KEY `FK4AFD4ACE28F13B88` (`fk_department_id`),
CONSTRAINT `FK4AFD4ACE28F13B88` FOREIGN KEY (`fk_department_id`) REFERENCES `department` (`department_id`)
ENGINE=InnoDB;
Так значит, что мы должны будем заботиться об удалении и изменении подчиненных записей сами? Нет не должны: hibernate сделает это и многое другое за нас. Надо только правильно настроить каскадные операции (и как вы уже поняли, что эти каскадные операции с каскадами в СУБД не имеют ничего общего). Но сначала пример:
public static void main(String[] args) {
Configuration configuration = new Configuration().configure();
SessionFactory factory = configuration.buildSessionFactory();
Session ses = factory.openSession();
ses.beginTransaction();
Employee jim = new Employee("jim");
Department managers = new Department("managers");
managers.getEmployies().add (jim);
jim.setDepartment(managers);
ses.saveOrUpdate(managers);
ses.saveOrUpdate(jim);
ses.getTransaction().commit();
}
mysql> select * from employee; +-------------+------+------------------+ | employee_id | fio | fk_department_id | +-------------+------+------------------+ | 1 | jim | 1 | +-------------+------+------------------+ 1 row in set (0.00 sec) mysql> select * from department; +---------------+----------+ | department_id | caption | +---------------+----------+ | 1 | managers | +---------------+----------+ 1 row in set (0.00 sec)
Теперь попробуем другой сценарий: все как раньше но при сохранении я укажу другой порядок сохранения объектов: сначала сотрудника, затем отдел. Тут я надеюсь, что будет ошибка: ведь на уровне базы стоит запрет, что нельзя сохранить сначала сотрудника (ссылающегося на еще не существующий отдел), и только затем сам отдел:
ses.saveOrUpdate(jim);
ses.saveOrUpdate(managers);
Exception in thread "main" org.hibernate.PropertyValueException:
not-null property references a null or transient value: experimental.business.Employee.department
ses.saveOrUpdate(managers);
//ses.saveOrUpdate(jim);
Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient
instance - save the transient instance before flushing: experimental.business.Employee
А как насчет удаления отдела или сотрудника?
После завершения первой транзакции я снова начинаю ее, загружаю отдел под номером 1 и пытаюсь удалить его:
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
ses.delete(managers_2);
ses.getTransaction().commit();
Exception in thread "main" org.hibernate.exception.ConstraintViolationException:
Could not execute JDBC batch update
…… пропущено без потери смысла ….
Caused by: java.sql.BatchUpdateException: Column 'fk_department_id' cannot be null
В следующем примере я для полной красоты добавил два отдела (дизайнеры и менеджеры) и трех человек персонала, первоначально зачисленных в отдел менеджмента, затем они должны быть переведены к дизайнерам:
public static void main(String[] args) {
Configuration configuration = new Configuration().configure();
SessionFactory factory = configuration.buildSessionFactory();
Session ses = factory.openSession();
ses.beginTransaction();
Employee jim = new Employee("jim");
Employee tom = new Employee("tom");
Employee ron = new Employee("ron");
Department managers = new Department("managers");
Department designers = new Department("designers");
managers.getEmployies().add(jim);
managers.getEmployies().add(tom);
managers.getEmployies().add(ron);
jim.setDepartment(managers);
tom.setDepartment(managers);
ron.setDepartment(managers);
ses.saveOrUpdate(managers);
ses.saveOrUpdate(designers);
ses.saveOrUpdate(jim);
ses.saveOrUpdate(tom);
ses.saveOrUpdate(ron);
ses.getTransaction().commit();
System.out.println("------------------------------------------");
// ---------------------------ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
Department designers_2 = (Department) ses.load(Department.class, 2);
Employee[] employies = managers_2.getEmployies().toArray(new Employee[]{});
for (Employee employy : employies) {
managers_2.getEmployies().remove(employy);
designers_2.getEmployies().add(employy);
employy.setDepartment(designers_2);
}ses.delete(managers_2);
ses.getTransaction().commit();
}
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
Department designers_2 = (Department) ses.load(Department.class, 2);
Employee[] employies = managers_2.getEmployies().toArray(new Employee[]{});
for (Employee employy : employies) {
managers_2.getEmployies().remove(employy);
employy.setDepartment(null);
ses.delete(employy);
}ses.delete(managers_2);
ses.getTransaction().commit();
Caused by: java.sql.BatchUpdateException: Column 'fk_department_id' cannot be null
Удалить отдел вместе с сотрудниками я не смог, только переместить в другой отдел. Более того, непонятно зачем столько городить проблем с этим hibernate, если только на старом добром sql перемещение сотрудников из одного отдела в другой занимало одну строчку кода. А может быть все не так уж и просто? И hibernate может взять на себя эти рутинные действия?
В одной замечательной книжке есть следующая фраза:
Hibernate supports ten different types of cascades that can be applied to many-to-one associations as well as collections. The default cascade is none. Each cascade strategy specifies the operation or operations that should be propagated to child entities.
В файле department.hbm.xml я заменил декларацию set-а на следующую:
<set name="employies" cascade="all"><!-- для организации связи между таблицами, нужно поместитьв класс зависящий от главной таблицы внешний ключ --><key column="fk_department_id" /><one-to-many class="Employee" /></set>А в файл employee.hbm.xml были внесены такие правки:
<source lang="xml"><many-to-one name="department" class="Department" column="fk_department_id" not-null="true" cascade="all"/>
Session ses = factory.openSession();
ses.beginTransaction();
Employee jim = new Employee("jim");
Employee tom = new Employee("tom");
Employee ron = new Employee("ron");
Department managers = new Department("managers");
managers.getEmployies().add(jim);
managers.getEmployies().add(tom);
managers.getEmployies().add(ron);
jim.setDepartment(managers);
tom.setDepartment(managers);
ron.setDepartment(managers);
ses.saveOrUpdate(jim);
ses.getTransaction().commit();
Поведение, которое будет, если убрать каскад от сотрудника к отделу.
ses.saveOrUpdate(managers);// а так я сохраню отдел и всех его сотрудников
//ses.saveOrUpdate(jim); // это вызывает ошибку, ведь указания того,// что отделы привязанные к сотруднику также нужно сохранить нет
Employee jim = new Employee("jim");
Employee tom = new Employee("tom");
Employee ron = new Employee("ron");
Department managers = new Department("managers");
managers.getEmployies().add(jim);
jim.setDepartment(managers);
tom.setDepartment(managers);
ron.setDepartment(managers);
// а вот привязку к отделу других сотрудников делать нельзя т.к. каскадного сохранения от отдела к ним - нет//managers.getEmployies().add(tom);//managers.getEmployies().add(ron);ses.saveOrUpdate(jim); // сохранение сотрудника заставляет сохранить и его отдел
ses.getTransaction().commit();
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
ses.delete(managers_2);
ses.getTransaction().commit();
Caused by: java.sql.BatchUpdateException: Column 'fk_department_id' cannot be null
Для этого я в корень своего classpath поместил файли log4j.properties (не забудьте подключить и библиотеку log4j). В нем я включаю журналирование:
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=warn, stdout
log4j.logger.javax.naming=none
#log4j.logger.org.hibernate=info
log4j.logger.org.hibernate=warn
### log HQL query parser activity
#log4j.logger.org.hibernate.hql.ast.AST=debug
### log just the SQL
log4j.logger.org.hibernate.SQL=debug
### log JDBC bind parameters ###
log4j.logger.org.hibernate.type=debug
### log schema export/update ###
#log4j.logger.org.hibernate.tool.hbm2ddl=debug
### log HQL parse trees
#log4j.logger.org.hibernate.hql=debug
### log cache activity ###
#log4j.logger.org.hibernate.cache=debug
### log transaction activity
#log4j.logger.org.hibernate.transaction=debug
### log JDBC resource acquisition
#log4j.logger.org.hibernate.jdbc=debug
### enable the following line if you want to track down connection ###
### leakages when using DriverManagerConnectionProvider ###
#log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace12:20:10,250 DEBUG SQL:401 - insert into Department (caption) values (?) 12:20:10,265 DEBUG StringType:133 - binding 'managers' to parameter: 1 12:20:10,265 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 12:20:10,265 DEBUG StringType:133 - binding 'jim' to parameter: 1 12:20:10,281 DEBUG IntegerType:133 - binding '1' to parameter: 2 12:20:10,281 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 12:20:10,281 DEBUG StringType:133 - binding 'ron' to parameter: 1 12:20:10,281 DEBUG IntegerType:133 - binding '1' to parameter: 2 12:20:10,281 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 12:20:10,281 DEBUG StringType:133 - binding 'tom' to parameter: 1 12:20:10,281 DEBUG IntegerType:133 - binding '1' to parameter: 2 12:20:10,281 DEBUG SQL:401 - insert into Department (caption) values (?) 12:20:10,281 DEBUG StringType:133 - binding 'designers' to parameter: 1 12:20:10,296 DEBUG SQL:401 - update Employee set fk_department_id=? where employee_id=? 12:20:10,296 DEBUG IntegerType:133 - binding '1' to parameter: 1 12:20:10,296 DEBUG IntegerType:133 - binding '1' to parameter: 2 12:20:10,296 DEBUG SQL:401 - update Employee set fk_department_id=? where employee_id=? 12:20:10,312 DEBUG IntegerType:133 - binding '1' to parameter: 1 12:20:10,312 DEBUG IntegerType:133 - binding '2' to parameter: 2 12:20:10,312 DEBUG SQL:401 - update Employee set fk_department_id=? where employee_id=? 12:20:10,312 DEBUG IntegerType:133 - binding '1' to parameter: 1 12:20:10,312 DEBUG IntegerType:133 - binding '3' to parameter: 2 ------------------------------------------ 12:20:10,343 DEBUG SQL:401 - update Employee set fk_department_id=null where fk_department_id=? 12:20:10,343 DEBUG IntegerType:133 - binding '1' to parameter: 1 12:20:10,343 WARN JDBCExceptionReporter:77 - SQL Error: 1048, SQLState: 23000 12:20:10,343 ERROR JDBCExceptionReporter:78 - Column 'fk_department_id' cannot be null
ses.saveOrUpdate(managers);
ses.saveOrUpdate(jim); // сохранение сотрудника заставляет сохранить и его отдел
ses.saveOrUpdate(designers);
//Добавляем новый отдел, пока все путем 12:20:10,250 DEBUG SQL:401 - insert into Department (caption) values (?) 12:20:10,265 DEBUG StringType:133 - binding 'managers' to parameter: 1 // в отделе мы узнали, что в нем есть сотрудники, отлично добавляем сотрудника Джима 12:20:10,265 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 12:20:10,265 DEBUG StringType:133 - binding 'jim' to parameter: 1 12:20:10,281 DEBUG IntegerType:133 - binding '1' to parameter: 2 // обратите внимание на предыдущую строку, второй параметр fk_department_id уже знает, что Джима нужно поместить в отдел номер 1. // далее тривиально сохраняем Рона и Тома (также в отдел номер 1) 12:20:10,281 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 12:20:10,281 DEBUG StringType:133 - binding 'ron' to parameter: 1 12:20:10,281 DEBUG IntegerType:133 - binding '1' to parameter: 2 12:20:10,281 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 12:20:10,281 DEBUG StringType:133 - binding 'tom' to parameter: 1 12:20:10,281 DEBUG IntegerType:133 - binding '1' to parameter: 2 // добавляем еще один отдел дизайнеров, он особой роли не играет 12:20:10,281 DEBUG SQL:401 - insert into Department (caption) values (?) 12:20:10,281 DEBUG StringType:133 - binding 'designers' to parameter: 1 // А это еще что такое? Зачем нужно обновлять сотрудника Джима, устанавливая ему значение поля fk_department_id равным 1, // ведь Джим уже в составе отдела номер 1. 12:20:10,296 DEBUG SQL:401 - update Employee set fk_department_id=? where employee_id=? 12:20:10,296 DEBUG IntegerType:133 - binding '1' to parameter: 1 12:20:10,296 DEBUG IntegerType:133 - binding '1' to parameter: 2
------------------------------------------ 12:20:10,343 DEBUG SQL:401 - update Employee set fk_department_id=null where fk_department_id=? 12:20:10,343 DEBUG IntegerType:133 - binding '1' to parameter: 1
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
Employee jim_2= (Employee) ses.load(Employee.class, 1);
ses.delete (jim_2);
ses.delete(managers_2);
ses.getTransaction().commit();
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
Employee jim_2= (Employee) ses.load(Employee.class, 1);
managers_2.getEmployies().remove(jim_2);
jim_2.setDepartment(null);
ses.delete(managers_2);
ses.getTransaction().commit();
Exception in thread "main" org.hibernate.PropertyValueException: not-null property references a null
or transient value: experimental.business.Employee.department
Department managers_2 = (Department) ses.load(Department.class, 1);
Employee jim_2= (Employee) ses.load(Employee.class, 1);
managers_2.getEmployies().remove(jim_2);
jim_2.setDepartment(null);
ses.delete(jim_2);
ses.delete(managers_2);
Caused by: java.sql.BatchUpdateException: Column 'fk_department_id' cannot be null
Как вывод: нам нужно подстроить hibernate под правила Б.Д. Начнем с того, что поймем, что в базах данных нет двусторонних связей, таких как я спроектировал ранее. Связь реализуется от подчиненного к главному и точка. Например, когда я выполняю типовое назначение сотрудника в отдел, то делаю так:
managers.getEmployies().add(jim);
jim.setDepartment(managers);
14:10:32,453 DEBUG SQL:401 - insert into Department (caption) values (?) --- и через много-много строк --- 14:10:32,484 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:10:32,515 DEBUG SQL:401 - update Employee set fk_department_id=? where employee_id=?
Итак, новый вид файла маппинга для отдела будет выглядеть так:
<set name="employies" cascade="all" inverse="true"><!-- для организации связи между таблицами, нужно поместитьв класс зависящий от главной таблицы внешний ключ --><key column="fk_department_id" /><one-to-many class="Employee" /></set>
ses.saveOrUpdate(managers);
ses.saveOrUpdate(jim);
ses.saveOrUpdate(jim);
ses.saveOrUpdate(managers);
14:18:02,406 DEBUG SQL:401 - insert into Department (caption) values (?) 14:18:02,437 DEBUG StringType:133 - binding 'managers' to parameter: 1 14:18:02,437 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:18:02,437 DEBUG StringType:133 - binding 'tom' to parameter: 1 14:18:02,437 DEBUG IntegerType:133 - binding '1' to parameter: 2 14:18:02,437 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:18:02,453 DEBUG StringType:133 - binding 'ron' to parameter: 1 14:18:02,453 DEBUG IntegerType:133 - binding '1' to parameter: 2 14:18:02,453 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:18:02,453 DEBUG StringType:133 - binding 'jim' to parameter: 1 14:18:02,453 DEBUG IntegerType:133 - binding '1' to parameter: 2 14:18:02,453 DEBUG SQL:401 - insert into Department (caption) values (?) 14:18:02,453 DEBUG StringType:133 - binding 'designers' to parameter: 1
ses.beginTransaction();
Employee jim = new Employee("jim");
Employee tom = new Employee("tom");
Employee ron = new Employee("ron");
Department managers = new Department("managers");
// я не могу не назначить сотруднику отдел т.к. у меня стоит ограничение not-null// и оно проверяется еще до отправки данных в СУБДjim.setDepartment(managers);
tom.setDepartment(managers);
ron.setDepartment(managers);
managers.getEmployies().add(tom);
managers.getEmployies().add(ron);
//managers.getEmployies().add(jim);ses.saveOrUpdate(jim);
ses.getTransaction().commit();
14:25:45,656 DEBUG SQL:401 - insert into Department (caption) values (?) 14:25:45,671 DEBUG StringType:133 - binding 'managers' to parameter: 1 14:25:45,687 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:25:45,687 DEBUG StringType:133 - binding 'tom' to parameter: 1 14:25:45,687 DEBUG IntegerType:133 - binding '1' to parameter: 2 14:25:45,687 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:25:45,703 DEBUG StringType:133 - binding 'ron' to parameter: 1 14:25:45,703 DEBUG IntegerType:133 - binding '1' to parameter: 2 14:25:45,703 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:25:45,703 DEBUG StringType:133 - binding 'jim' to parameter: 1 14:25:45,703 DEBUG IntegerType:133 - binding '1' to parameter: 2
Employee jim = new Employee("jim");
Employee tom = new Employee("tom");
Employee ron = new Employee("ron");
Department managers = new Department("managers");
Department designers = new Department("designers");
jim.setDepartment(managers);
tom.setDepartment(managers);
ron.setDepartment(designers);
managers.getEmployies().add(tom);
managers.getEmployies().add(ron);
ses.saveOrUpdate(jim);
14:31:59,359 DEBUG SQL:401 - insert into Department (caption) values (?) 14:31:59,375 DEBUG StringType:133 - binding 'managers' to parameter: 1 14:31:59,390 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:31:59,390 DEBUG StringType:133 - binding 'tom' to parameter: 1 14:31:59,390 DEBUG IntegerType:133 - binding '1' to parameter: 2 14:31:59,390 DEBUG SQL:401 - insert into Department (caption) values (?) 14:31:59,390 DEBUG StringType:133 - binding 'designers' to parameter: 1 14:31:59,390 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:31:59,390 DEBUG StringType:133 - binding 'ron' to parameter: 1 14:31:59,390 DEBUG IntegerType:133 - binding '2' to parameter: 2 14:31:59,406 DEBUG SQL:401 - insert into Employee (fio, fk_department_id) values (?, ?) 14:31:59,406 DEBUG StringType:133 - binding 'jim' to parameter: 1 14:31:59,406 DEBUG IntegerType:133 - binding '1' to parameter: 2
На этом с сохранением все, переходим к удалению.
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
ses.delete(managers_2);
ses.getTransaction().commit();
14:36:09,343 DEBUG SQL:401 - delete from Employee where employee_id=? 14:36:09,343 DEBUG IntegerType:133 - binding '3' to parameter: 1 14:36:09,359 DEBUG SQL:401 - delete from Employee where employee_id=? 14:36:09,359 DEBUG IntegerType:133 - binding '1' to parameter: 1 14:36:09,359 DEBUG SQL:401 - delete from Employee where employee_id=? 14:36:09,359 DEBUG IntegerType:133 - binding '2' to parameter: 1 14:36:09,359 DEBUG SQL:401 - delete from Department where department_id=? 14:36:09,359 DEBUG IntegerType:133 - binding '1' to parameter: 1
ses = factory.openSession();
ses.beginTransaction();
Employee jim_2 = (Employee) ses.load(Employee.class, 1);
ses.delete(jim_2);
ses.getTransaction().commit();
14:37:29,031 DEBUG SQL:401 - delete from Employee where employee_id=? 14:37:29,031 DEBUG IntegerType:133 - binding '1' to parameter: 1 14:37:29,031 DEBUG SQL:401 - delete from Employee where employee_id=? 14:37:29,031 DEBUG IntegerType:133 - binding '3' to parameter: 1 14:37:29,031 DEBUG SQL:401 - delete from Employee where employee_id=? 14:37:29,031 DEBUG IntegerType:133 - binding '2' to parameter: 1 14:37:29,046 DEBUG SQL:401 - delete from Department where department_id=? 14:37:29,046 DEBUG IntegerType:133 - binding '1' to parameter: 1
<many-to-one name="department" class="Department" column="fk_department_id" not-null="true" cascade="save-update" />
Совсем забыл.
Небольшое дополнение: не забывайте, что при подтверждении транзакции все объекты, которые прошли через сессию (были загружены или созданы) будут проверяться на предмет "истинной духовной чистоты", т.е. были или не были ли они изменены. В последнем примере я удаляю Джима загружая его ОДНОГО из сессии (сессия у меня новая) и ссылка на отдел через сессию не проходит (помним, что по-умолчанию в hibernate свойства many-to-one равно как и коллекции загружаются "лениво"). Поэтому при удалении все получилось отлично. Но вот если сделать так:
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
Set<Employee> employies = managers_2.getEmployies();
Employee jim = employies.iterator().next();
ses.delete(jim);
Exception in thread "main" org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [experimental.business.Employee#1]
managers_2.getEmployies().remove(jim);
<class name="Department" dynamic-insert="true" dynamic-update="true" ><id name="department_id"><generator class="native" /></id><property name="caption" /><set name="employies" cascade="all-delete-orphan" inverse="true" ><key column="fk_department_id" /><one-to-many class="Employee" /></set></class>
ses.beginTransaction();
Department managers_2 = (Department) ses.load(Department.class, 1);
Set<Employee> employies = managers_2.getEmployies();
employies.remove(employies.iterator().next());
ses.getTransaction().commit();
ses.beginTransaction();
Employee jim = new Employee("jim");
Employee tom = new Employee("tom");
Employee ron = new Employee("ron");
Department managers = new Department("managers");
jim.setDepartment(managers);
tom.setDepartment(managers);
ron.setDepartment(managers);
managers.getEmployies().add(tom);
managers.getEmployies().add(ron);
managers.getEmployies().add(jim);
ses.saveOrUpdate(jim);
ses.getTransaction().commit();
Exception in thread "main" org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: experimental.business.EmployeeСобственно, говоря, такое ограничение - совсем и не ограничение, т.к. в большинстве нормально спроектированных приложений, вы сначала создадите и сохраните отдел и только затем начнете его наполнять сотрудниками (ну и увольнять их соответственно).
| « Системы управления версиями для программистов и не только. Часть 4 | Hibernate: Set-ы, bag-и и все, все, все » |