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=trace
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 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-и и все, все, все » |