java web实战学习第一天
搭建maven项目
具体步骤参照 https://www.jianshu.com/p/49c01353a869 中的说明完成
搭建spring hibernate 环境
具体步骤参照 https://www.jianshu.com/p/788733d003a0 中的说明完成
文档中的依赖包,那么多,并没有一次都添加,只添加最基本的,以后用到哪个,加哪个。
项目目录建立
image.png创建第一个controller,第一个action。hello world!能够正常显示,说明配置成功。
数据库设计,建立数据实体类
数据库表是用其他项目的,实体类是自己创建的,共增加了11个实体类,这里涉及到多个“注解”标签的使用,还有bibernate表关系,一对一,一对多多对一双向关联。虽然没有其他的关联模式,但是下边对都整理出来。
实体类中接触到的标签:
@Entity//声明当前类为hibernate映射到数据库的实体类
* @Entity(name="tableName") - 必须,注解将一个类声明为一个实体bean。
* 属性: name - 可选,对应数据库中的一个表。若表名与实体类名相同,则可以省略。
@Table(name="User")//为实体Bean指定对应的数据表
* @Table(name="",catalog="",schema="") - 可选,通常和@Entity 配合使用,只能标注在实体的 class 定义处,表示实体对应的数据库表的信息。
* 属性: name - 可选,表示表的名称,默认地,表名和实体名称一致,只有在不一致的情况下才需要指定表名
* catalog - 可选,表示Catalog名称,默认为 Catalog("").
* schema - 可选 , 表示 Schema 名称 , 默认为Schema("").
@Id //声明当前字段为主键
@GeneratedValue(strategy = GenerationType.AUTO,generator="")//注解来定义生成主键的策略
* Strategy - 表示主键生成策略,取值有:
* GenerationType.TABLES 当前主键的值单独保存到一个数据库的表中,结合@TableGenerator使用
* GenerationType.SEQUENCE 利用底层数据库提供的序列生成标识符
* GenerationType.IDENTITY 根据数据库的Identity字段生成,采取数据库的自增策略
* GenerationType.AUTO 根据不同数据库自动选择合适的id生成方案,这里使用mysql,为递增型
*generator - 表示主键生成器的名称,这个属性通常和ORM框架相关,如:Hibernate 可以指定 uuid 等主键生成方式
@Column(table = "User", columnDefinition="varchar",length = 255, nullable = true)
可将属性映射到列,使用该注解来覆盖默认值,@Column描述了数据库表中该字段的详细定义,
这对于根据 JPA 注解生成数据库表结构的工具非常有作用。
name - 可选,表示数据库表中该字段的名称,默认情形属性名称一致
nullable - 可选,表示该字段是否允许为 null,默认为 true
unique - 可选,表示该字段是否是唯一标识,默认为 false
length - 可选,表示该字段的大小,仅对 String 类型的字段有效,默认值255.
insertable - 可选,表示在ORM框架执行插入操作时,该字段是否应出现INSETRT语句中,默认为 true
updateable - 可选,表示在ORM 框架执行更新操作时,该字段是否应该出现在 UPDATE 语句中,默认为 true. 对于一经创建就不可以更改的字段,该属性非常有用,如对于 birthday字段。
columnDefinition - 可选,表示该字段在数据库中的实际类型。通常ORM 框架可以根据属性类型自动判断数据库中字段的类型,但是对于Date类型仍无法确定数据库中字段类型究竟是 DATE,TIME还是TIMESTAMP. 此外,String 的默认映射类型为 VARCHAR, 如果要将 String 类型映射到特定数据库的 BLOB或 TEXT 字段类型,该属性非常有用。
@Version //注解用于支持乐观锁版本控制,通过这种方式可添加对乐观锁定的支持
@Email(message = "邮箱格式错误")
@Basic(fetch = FetchType.EAGER, optional = true)//用于声明属性的存取策略
* @Basic 基本属性类型映射,注解于非Static 非transient的属性,
* 这时候我们可以为其声明抓取策略等属性
* fetch: 获取策略,当以session.get()方法获取数据库对象时:
* FetchType.LAZY为懒加载,会在第一次使用该属性(如调用getAge()方法)时才获取。
* FetchType.EAGER为即时加载。
* optional:表示当前属性是否可选,默认为true;如果为false,则在持久化到数据库时,如果此属性为null,则会失败
@Transient - 可选,自定义字段,表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性,如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则ORM 框架默认其注解为 @Basic
示例 :
// 根据 birth 计算出 age 属性
@Transient
public int getAge() {
return getYear(new Date()) - getYear(birth);
}
@Lob
* @Lob 注解表示属性将被持久化为Blob或者Clob类型,
* 具体取决于属性的类型, java.sql.Clob, Character[], char[] 和 java.lang.String这些类型的属性都被持久化为Clob类型,
* 而java.sql.Blob, Byte[], byte[] 和 serializable类型则被持久化为Blob类型.
@Temporal(TemporalType.TIME)//用于定义映射到数据库的时间精度
* @Temporal(TemporalType=DATE) 日期
* @Temporal(TemporalType=TIME) 时间
* @Temporal(TemporalType=TIMESTAMP) 两者兼具
表关系模式:
?对于多对一(不管是单向关联还是双向关联),都只需要在多的一端使用@ManyToONe修饰关联实体的属性
单向: 关系写哪边,就由谁管理。
一对多,多对一
1. 多对一:关系映射再多端维护。如:论文类别--->类别
2. 一对多: 关系映射再一端维护。如:用户--->电子邮件
3. 数据库表设计:都是再多的一端建立外键
对于单向的1—N关联关系。只需要在1的一端加入set类型的成员变量,记录所有的关联的实体。
N的端不维护关系,没得变化~
//多对一:多个人共住一个地址。地址实体不维护关系
// 定义该Person实体关联的Address实体
@ManyToOne(targetEntity=Address.class)
// 映射外键列,指定外键列的列名为address_id、不允许为空
@JoinColumn(name="address_id" , nullable=false)
@Cascade(CascadeType.ALL)
private Address address;
//一对多:一个人有多个房子。
// 定义该Person实体所有关联的Address实体,没有指定cascade属性
@OneToMany(targetEntity=Address.class)
// 映射外键列,此处映射的外键列将会添加到关联实体对应的数据表中,为啥呢?
@JoinColumn(name="person_id" , referencedColumnName="person_id")
private Set<Address> addresses = new HashSet<>();
一对一
//一对一:一个人住一个地址。
// 定义该Person实体关联的Address实体
@OneToOne(targetEntity=Address.class)
// 映射名为address_id的外键列,参照关联实体对应表的addres_id主键列
@JoinColumn(name="address_id", referencedColumnName="address_id" , unique=true)
private Address address;
地址肯定是独一无二的嘛,对不对!增加unique约束~
多对多
双向:
一对多,多对一
1. 不要让1的端(也就是有Set这个集合的那一个)控制关联的关系。而是使用N的一端控制关联关系,一般由多方管理。
2. 两端都需要增加对关联属性的访问,N的一端增加引用关联实体的属性,1的一端增加集合属性,集合元素为关联的实体.
3. 只需要在N的一端增加一个外键列就好啦。因此在多的一端,使用@JoinColumn来映射外键列。
4. 我们使用@OneToMany的时候中应该指明mappedBy (映射由谁控制) 属性,表明当前的实体不能控制关联的关系
5. 非关系控制实体类,不能指明@JoinColumn 或@JointTable 来修饰关联实体的属性
一对多,多对一双向实例:
//一端,非控制端
// 定义该Person实体所有关联的Address实体
// 指定mappedBy属性表明该Person实体不控制关联关系
//这里的person是address中的person
@OneToMany(targetEntity=Address.class, mappedBy="person")
private Set<Address> addresses = new HashSet<>();
//多端,关系控制端
// 定义该Address实体关联的Person实体
@ManyToOne(targetEntity=Person.class)
// 定义名为person_id外键列,该外键列引用person_inf表的person_id列。
@JoinColumn(name="person_id" , referencedColumnName="person_id", nullable=false)
private Person person;
多对多
1. 双向的N-N关联,两端都要set分别使用@ManyToMany 都要使用@JoinTable 显示的连接在一起
2. 哪端想放弃控制关联关系,可以使用mappBy。也就不能使用@joinTable啦
//person实体类
@ManyToMany(targetEntity=Address.class)
// 映射连接表,指定连接表的表名为person_address
@JoinTable(name="person_address",
// 映射连接表中名为person_id的外键列,
// 该列参照当前实体对应表的person_id主键列
joinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id"),
// 映射连接表中名为address_id的外键列,
// 该列参数当前实体的关联实体对应表的address_id主键列
inverseJoinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id")
)
private Set<Address> addresses = new HashSet<>();
//address实体类
// 定义该Address实体所有关联的Person实体
@ManyToMany(targetEntity=Person.class)
// 映射连接表,指定连接表的表名为person_address
@JoinTable(name="person_address",
// 映射连接表中名为address_id的外键列,
// 该列参照当前实体对应表的address_id主键列
joinColumns=@JoinColumn(name="address_id", referencedColumnName="address_id"),
// 映射连接表中名为person_id的外键列,
// 该列参照当前实体对应表的person_id主键列
inverseJoinColumns=@JoinColumn(name="person_id", referencedColumnName="person_id")
)
private Set<Person> persons = new HashSet<>();
上边的代码例子是谁都没有放弃控制权;没毛病,这样是可以的,下边看一下具体使用:
我先持久化了person,创建关联关系。最后持久化地址。
// 创建一个Person对象
Person p = new Person();
// 持久化Person对象(对应于插入主表记录)
session.save(p);
// 创建一个瞬态的Address对象
Address a = new Address("遵义市海龙坝");
// 先设置Person和Address之间的关联关系
a.getPersons().add(p);
// 再持久化Address对象
session.persist(a);
上边的例子也可以这样:先创建插入地址,再插入到关系表中。
session.save(a);
a.getPersons().add(p);
一对一:
基本上一对一都是基于外键使用,很少有共享主键的使用场景,所以暂不讨论。
总有一方要先把实体存到数据库中,然后另一方才有外键存在呢。
放弃控制权的一方,可以不考虑address属性,增加新的记录
// 定义该Person实体关联的Address实体
@OneToOne(targetEntity=Address.class , mappedBy="person")
private Address address;
//实际使用
Person p=new Person();
session.save(p)没有压力
控制关联关系一方, 增加记录有先后顺序的。必须先有person再有address
// 定义该Address实体关联的Person实体
@OneToOne(targetEntity=Person.class)
// 用于映射person_id外键列,参照person_inf表的person_id列
// 指定了unique=true表明是1-1关联
@JoinColumn(name="person_id" , referencedColumnName="person_id"
, unique=true)
private Person person;
//现实中新增address的例子
Address address=new Address();
address.addPerson(持久化的Person);
session.save(address)
关联映射常用注解
@OneToOne、@OneToMany、@ManyToOne、ManyToMany的共有属性:
-
fetch - 配置加载方式。取值有:
Fetch.EAGER - 及时加载,多对一默认是Fetch.EAGER
Fetch.LAZY - 延迟加载,一对多默认是Fetch.LAZY -
cascade - 设置级联方式,取值有:
CascadeType.PERSIST - 保存
CascadeType.REMOVE - 删除
CascadeType.MERGE - 修改
CascadeType.REFRESH - 刷新
CascadeType.ALL - 全部
举个例子:Order 和OrderItem有级联关系,那么删除Order时将同时删除它所对应的OrderItem对象。而如果OrderItem还和其他的对象之间有级联关系,那么这样的操作会一直递归执行下去。 -
targetEntity - 配置集合属性类型,如:@OneToMany(targetEntity=Book.class)
-
mappedBy :@OneToMany(mappedBy="对方") //反向配置,对方管理
定义类之间的双向关系。如果类之间是单向关系,不需要提供定义,如果类和类之间形成双向关系,我们就需要使用这个属性进行定义,否则可能引起数据一致性的问题。该属性的值是“多”方class里的“一”方的变量名,也就是多方表中的外键名称。
一旦被注解@mapperBy,即放弃了维护关联关系,而@JoinColumn注解的都是在“主控方”,因而我们需要注解在Article类中.
对于不需要维护这种关系的从表则通过mappedBy属性进行声明,mappedBy的值指向主体(owner)端的对象。如:mappedby="role" -
optional属性
是定义该关联类是否必须存在,值为false 时,关联类双方都必须存在,如果关系被维护端不存在,查询的结果为null。值为true 时, 关系被维护端可以不存在,查询的结果仍然会返回关系维护端,在关系维护端中指向关系被维护端的属性为null。optional属性的默认值是true。
optional 属性实际上指定关联类与被关联类的join 查询关系,如optional=false 时join 查询关系为inner join,optional=true 时join 查询关系为leftjoin。
@JoinColumn - 可选,用于描述一个关联的字段。Hibernate使用@JoinColumn来修饰代表关联实体的属性,用于映射底层的外键列。
@JoinColumn和@Column类似,这里描述的不是一个简单字段,而是一个关联字段,
例如描述一个@ManyToOne 的字段。
属性:
name - 该字段的名称,由于@JoinColumn描述的是一个关联字段,如ManyToOne, 则默认的名称由其关联的实体决定。
例如,实体 Order 有一个user 属性来关联实体 User, 则 Order 的 user 属性为一个外键, 其默认的名称为实体
User的名称 + 下划线 + 实体User的主键名称
unique=true - 外键的值是唯一的(unique),不可重复,与另一类的主键一致。