上面两篇博客介绍了cascade和inverse级联关系作用,而constrained属性是在主键映射一对一关系的时候会用到的
一: 建立公民对象People与身份证对象IDcard一对一主键关联
公民表与身份证表的关联关系
公民实体对象与身份证实体对象之间的依赖关系
tb_people.hbm.xml的配置文件(注意:类对象中的所有属性都必须在配置文件中存在,否则会报错找不到getter和setter方法的错误)
<hibernate-mapping package="com.book.web3">
<class name="People" table="tb_people">
<id name="id" column="id" type="int" >
<generator class="assigned" /> <!--generator这个坑,如果指定class为native会导致外部程序设置的主键id无效,且根据数据库自行判断采用自增长式 -->
</id>
<property name="name" type="string">
<column name="name"/>
</property>
<property name="sex" type="string" column="sex"></property>
<property name="age" type="int" column="age"></property>
<one-to-one name="idcard" class="com.book.web3.IDcard" cascade="all"></one-to-one>
</class>
</hibernate-mapping>
tb_idcard.hbm.xml的配置文件
<hibernate-mapping package="com.book.web3">
<class name="IDcard" table="tb_idcard">
<id name="id" column="id" type="int" >
<generator class="assigned" /> <!--generator这个坑,如果指定class为native会导致外部程序设置的主键id无效,且根据数据库自行判断采用自增长式 -->
</id>
<property name="idcard_code" type="string" not-null="true">
<column name="idcard_code"/>
</property>
<one-to-one name="people" class="com.book.web3.People" constrained="true"></one-to-one>
</class>
</hibernate-mapping>
测试类代码:
Session session = null;
People people = new People();
people.setId(100);
people.setName("张三");
people.setSex("男");
people.setAge(18);
IDcard idcard = new IDcard();
idcard.setId(100);
idcard.setIdcard_code("4102586248962555");
// //这个就是设置相关联的对象
people.setIdcard(idcard); //不管是book还是factory设置维护级联关系,都要添加,否则在保存factory情况下导致book表的factoryid为null
try {
session = HibernateUtil.getSession();
//添加数据
session.beginTransaction();
session.save(people);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
System.out.print("数据库添加失败");
e.printStackTrace();
}finally{
HibernateUtil.closeSession();
}
以上测试类代码标红部分需要注意,因为没有在数据库表中建立唯一的外键关联属性,所以这里添加数据用两个表中的id属性作为外键关联属性,也就是说people中的id既是主键也是外键,所以值必须一样,否则报错
表数据删除测试代码
session = HibernateUtil.getSession();
People people = session.get(People.class, new Integer("100"));
session.delete(people);
session.flush();
结果如图:
二:下面介绍一下一对一有外键关联
在tb_people表中添加一个新字段card_id,作为该表的外键,同时需要保证该字段的唯一性,否则就不是一对一映射关系了,而是一对多映射关系,表关系如图
数据库表如图:
tb_people.hbm.xml的配置文件如下:
<hibernate-mapping package="com.book.web3">
<class name="People" table="tb_people">
<id name="id" column="id" type="int" >
<!-- 主键生成策略 -->
<generator class="assigned" />
</id>
<property name="name" type="string">
<column name="name"/>
</property>
<property name="sex" type="string" column="sex"></property>
<property name="age" type="int" column="age"></property>
<!--跟多对一一样,只是增加了一个unique属性。这样就指定了这端为一了。-->
<many-to-one name="idcard" column="card_id" unique="true" cascade="all"></many-to-one>
</class>
</hibernate-mapping>
上面标红部分cascade="all";如果不添加那么save和delete都不存在级联关系,也就是说保存一张表不会保存另外一张表的数据
tb_idcard.hbm.xml的配置文件如下:
<hibernate-mapping package="com.book.web3">
<class name="IDcard" table="tb_idcard">
<id name="id" column="id" type="int" >
<!-- 主键生成策略 -->
<generator class="assigned" />
</id>
<!-- 一些常规属性 -->
<property name="idcard_code" type="string" not-null="true" unique="true">
<column name="idcard_code"/>
</property>
<!-- 要注意property-ref这个属性,很重要,关键的地方就在这里。
property-ref:指定关联类的属性名,这个属性将会和本类的主键相对应。如果没有指定,
会使用对方关联类的主键来跟本类的主键比较,这里要注意不是关联表中的外键字段名。如果不指定这个属性,那么
一对一默认会使用主键去做对比。相当于原本我们
是可以通过本类的主键去和关联类的外键比较,然后来找到对应记录的,但是这里一对一中没有
column属性,所以该方法行不通,因此就想出了这种办法,不跟外键比,也不能跟主键比(因为不是主键关系),那么
就跟关联表中的一个属性比,也就是我们这个person中的idcard属性,为什么找得到呢,因为从person能找到idcard,那么
person中的idcard中就会有对应的值,我们跟该值比,也就能找到对应的person了。
class:person所在的类,这个也可以不写,hibernate会自动帮我们找到
-->
<one-to-one name="people" property-ref="idcard" class="com.book.web3.People" cascade="all"></one-to-one>
</class>
</hibernate-mapping>
上面标红部分cascade="all";如果不添加那么save和delete都不存在级联关系,也就是说保存一张表不会保存另外一张表的数据
测试类代码:
Session session = null;
People people = new People();
people.setId(98);
people.setName("杨幂");
people.setSex("女");
people.setAge(27);
IDcard idcard = new IDcard();
idcard.setId(200);
idcard.setIdcard_code("41087456625552555");
// //这个就是设置相关联的对象
people.setIdcard(idcard); //不管是book还是factory设置维护级联关系,都要添加,否则在保存factory情况下导致book表的factoryid为null
try {
session = HibernateUtil.getSession();
//添加数据
session.beginTransaction();
session.save(idcard); //当tb_people.hbm.xml的配置文件没有添加cascade时,该行必须添加而且必须保存在前
session.save(people);
session.getTransaction().commit();
} catch (Exception e) {
session.getTransaction().rollback();
System.out.print("数据库添加失败");
e.printStackTrace();
}finally{
HibernateUtil.closeSession();
}
中间测试实验时遇到一个问题,当配置文件没有配置cascade="all"属性时,保存数据时
session.save(idcard);
session.save(people);
两行必须添加,而且是必选先添加idcard,后添加people才不会报错,数据库的idcard_id是外键关联tb_idcard表的主键id,且可有相互查询关系,但是如果只是save一个表和delete一个表时不在级联另一张表,以下一一给出 cascade="all"添加和不添加的用例
一: tb_people.hbm.xml和tb_idcard.hbm.xml的配置文件都没有添加cascade属性时
1 、save关系上面也讲了,那么保存数据时,必须同时save(people) 和save(iscard),且save(iscard)在前,否则会报如下错误
ERROR SqlExceptionHelper Cannot add or update a child row: a foreign key constraint fails (`student`.`tb_people`, CONSTRAINT `FKo98i2l8ri698p0xiy66f7euoc` FOREIGN KEY (`card_id`) REFERENCES `tb_idcard` (`id`))
ERROR SessionImpl HHH000346: Error during managed flush [could not execute statement]
数据库添加失败org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:207)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3106)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2985)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3365)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:145)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:560)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:434)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
at com.book.web3.HibernateTest.onetoone(HibernateTest.java:53)
at com.book.web3.HibernateTest.main(HibernateTest.java:26)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`student`.`tb_people`, CONSTRAINT `FKo98i2l8ri698p0xiy66f7euoc` FOREIGN KEY (`card_id`) REFERENCES `tb_idcard` (`id`))
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.Util.getInstance(Util.java:408)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:936)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3973)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3909)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2680)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2484)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858)
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2079)
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2013)
at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5104)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1998)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
... 20 more
2 、删除delete关系,同时可以发现,删除也是一样不存在级联关系的,也就是说删除一张表的数据,另一张表的数据是不会删除的,但是查询关系可以通互查询的
3 、查询关系 通过People查询
session = HibernateUtil.getSession();
People person = (People)session.get(People.class,98);
System.out.println(person.getIdcard().getIdcard_code());
结果:
Hibernate:
select
people0_.id as id1_3_0_,
people0_.name as name2_3_0_,
people0_.sex as sex3_3_0_,
people0_.age as age4_3_0_,
people0_.card_id as card_id5_3_0_
from
tb_people people0_
where
people0_.id=?
Hibernate:
select
idcard0_.id as id1_2_0_,
idcard0_.idcard_code as idcard_c2_2_0_,
people1_.id as id1_3_1_,
people1_.name as name2_3_1_,
people1_.sex as sex3_3_1_,
people1_.age as age4_3_1_,
people1_.card_id as card_id5_3_1_
from
tb_idcard idcard0_
left outer join
tb_people people1_
on idcard0_.id=people1_.card_id
where
idcard0_.id=?
Hibernate:
select
people0_.id as id1_3_0_,
people0_.name as name2_3_0_,
people0_.sex as sex3_3_0_,
people0_.age as age4_3_0_,
people0_.card_id as card_id5_3_0_
from
tb_people people0_
where
people0_.card_id=?
41087456625552555
通过idcard查询
session = HibernateUtil.getSession();
IDcard idCard = (IDcard)session.get(IDcard.class, 200);
System.out.println(idCard.getPeople().getName());
结果:
Hibernate:
select
idcard0_.id as id1_2_0_,
idcard0_.idcard_code as idcard_c2_2_0_,
people1_.id as id1_3_1_,
people1_.name as name2_3_1_,
people1_.sex as sex3_3_1_,
people1_.age as age4_3_1_,
people1_.card_id as card_id5_3_1_
from
tb_idcard idcard0_
left outer join
tb_people people1_
on idcard0_.id=people1_.card_id
where
idcard0_.id=?
杨幂
二: tb_people.hbm.xml和tb_idcard.hbm.xml的配置文件都添加cascade属性时
session = HibernateUtil.getSession();
//添加数据
session.beginTransaction();
// session.save(idcard); //可以不必添加(因为设置了cascade属性,级联关系)
session.save(people);
session.getTransaction().commit();
删除关系也是一样,删除一张表,另一张表的数据也删除了
session = HibernateUtil.getSession();
People people = session.get(People.class, new Integer("99"));
session.delete(people);
session.flush();
三. constrained的作用
Hibernate文档上是这么写的:
constrained(约束) (可选) 表明该类对应的表对应的数据库表,和被关联的对象所对应的数据库表之间,通过一个外键引用对主键进行约束。这个选项影响save() 和delete() 在级联执行时的先后顺序(也在schema export tool中被使用)。
constrained默认值为false。
constrained只能在one-to-one的映射中使用,(一般在主表的映射中,有外键的那个表)。如果constrained=true, 则表明存在外键与关联表对应,并且关联表中肯定存在对应的键与其对应, 另外该选项最关键的是影响save和delete的先后顺序。例如增加的时候,如果constainted=true,则会先增加关联表,然后增加本表。 删除的时候反之。
one-to-one的单向关联中,如果constrained=false,则会在查询时就全部取出来,用left outer join的方式。如果constrained=true,hibernate即会延迟加载sql,只把主表的查出来,等有用到关联表的再发sql取。
one-to- one的双向关联中,必须设置constrained=true,要不然会有重复数据读,如2个表user,car;在位false时sql如 下:select * from user a left outer join car b on a.id=b.id left outer join on user c on a.id=c.id where a.id=? 删除的时候最好删除从表。
四、总结
学完之后,我们应该知道,
1 在外键关联中,如果不设置cascade属性,那么就两张外键关联表就不能通过一张表的数据的保存删除而影响另外一张表
2、主键关联的特点:一个表中的主键就是外键,指向另一个表中的主键,所以两张表的主键是相同的,但是有一个缺点,就是必须依赖另一张表的主键,这在有些业务逻辑上是行不通的
3、双向一对一主键关联非常的简单,其重点在主键id中的主键生成策略那块还有constrained属性的使用
4、双向一对一外键关联也非常简单,关键的地方就在<one-to-one>中property-ref的配置,注意这个的意思是配置关联类中的属性,而不是关联类中的外键字段名。
5、one-to-one默认是使用主键和主键进行比较来查询数据,所以其中并没有column这个属性。因为没有这个column属性,所以就外键关联中就需要用到第5点的property-ref的属性了。
到此,一对一关系就结束了,如果有什么不懂的问题,就在下面留言,然后在尽自己的力量帮你们解答把。