【JPA】从入门到高级

JPA教程:入门到高级



一、JPA的基本注解?

@Entity

写在类上,表示这个类是一个实体类,加了它之后,就表示当前类跟数据库中的表有对应关系了

@Table(name = “t_user”)

指定当前实体类对应哪一张表;

@Entity  //如果只加@Entity,其实也行,但是你数据库中对应的表名,也得是user,如果t_user,你需要用Table(name="t_user")来指定
public class User{
	...
}

@Id

用来指定实体类中哪个属性对应表中的注解,可以写在属性上,也可以写在属性的getter方法上;

@GenerateValue

用来指定主键的生成策略,总共有四种策略,通过strategy来指定到底使用哪种策略;(这个注解跟@Id一样,可以写在属性上,也可以写在属性的getter方法上)
1.@GenerateValue(strategy = GenerationType.AUTO)
AUTO是JPA默认的主键生成策略,你不加strategy = GenerationType.AUTO,他也是使用这个策略;这个策略下他会根据你表的情况自动生成主键,比如当他检测到你是mysql数据库,它就会自动给使用主键自增;
2.@GenerateValue(strategy = GenerationType.IDENTITY)
这个就是直接指定为主键自增了;
3.@GenerateValue(strategy = GenerationType.SEQUENCE)
这个就是通过序列生成主键,通常Oracle数据库用的多,除此之外你还需要通过@SequenceGenerator来指定序列名,告诉它到底使用哪个序列来生成主键; mysql是不支持这种方式的;
4.@GenerateValue(strategy = GenerationType.TABLE)
这个就是通过另外一个数据表来生成主键,这个比较麻烦,后面再阐述;

@GenerateValue
@Id
public Long getId(Long id){
	return this.id;
}

@Column

写在属性上,也可以写在属性的getter方法上;

@Column(name="last_name",length=50,nullable=false)
public String lastName;

这里表示指定属性lastName对应表中的last_name字段,还指定了该字段的长度为50,不能为空,nullable表示询问你该字段是否可以为空,false表示不能为空;
另外如果你没有通过length指定字段长度的话,那么JPA在通过实体类创建表时,String类型的属性就会被创建为varchar类型,并且给它一个默认的长度255,这就可能造成空间的浪费;

以下就是指定后的效果:
在这里插入图片描述

@Basic

你实体类的属性上,或者它对应的getter方法上,默认就会自带@Basic,就算你没写@Basic它也自带;
比如下面的例子:

public class User{
	//@Basic  就是说在这里,JPA会给你默认加一个@Basic,表示在创建表时,它会自动帮你创建一个名为lastname的字段;
	private String lastName;
}
public class User{
	//@Basic  在getter方法上,它也会默认加上@Basic
	public String getLastName(){
		return this.lastName;
	}
}

注意:@Basic加在属性上在本质上就是加在了getter方法上;

@Transient

这个注解的作用是:由于JPA会默认给你的实体类属性上加上@Basic注解,然后JPA再去给你这些属性创建字段;但是有时候你的实体类中,你会加一些不是表的字段的属性,此时我们就要用@Transient将其标注出来,让JPA在创建表时,忽略这些属性,否则JPA就会默认标注为@Basic;

注意:这个注解写在getter方法上也是可以的;

@Temporal

该注解主要用来指定Date类型的精确度,它有三种精确度:DATE,TIME,TIMESTAMP
分别表示精确到日期,精确到时分秒,精确到日期+时分秒
举例:

@Entity
@Table(name = "t_user")
public class User{
	private Date createTime;
	private Date birth;
}
User user = new User();
 user.setCreateTime(new Date());
 user.setBirth(new Date());
userRepository.save(user);

当你在创建表时,如果你没有给Date类型的属性加@Temporal注解,那么以后你往里面set值的时候:默认设置进去的时间,就是日期加时分秒的格式:比如下面这种;
在这里插入图片描述
但是我们在保存生日birth时,我们并不需要保存时分秒,只需要保存日期就行了,而创建时间则可以精确到秒;那么此时就产生了问题,@Temporal就派上用场了;

@Entity
@Table(name = "t_user")
public class User{
	@Temporal(TemporalType.TIMESTAMP) //这个表示当你往这个日期属性里设置值时,时间格式就是日期加时分秒的格式,yy-mm-dd HH:MM:SS
	private Date createTime;
	
	@Temporal(TemporalType.DATE) //表示时间格式仅为日期:yy-mm-dd
	private Date birth;

	@Temporal(TemporalType.TIME) //表示时间格式仅为时分秒:HH:MM:SS
	private Date clock;
}

二、JPA的api

三、JPA中的多表操作

1. 一对一关系

举例:身份证与用户的关系

@Entity
@Table(name = "t_user")
public class User{
	@Id
	@GenerateValue
	private Long id;
	
	@Column(name = "user_number")
	private String userNumber;
	
	@JoinToOne(name = "idcard_id")
	@oneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)  
	private IDCard idcard ;
}

@Entity
@Table(name = "t_user")
public class IDCard{
	@Id
	@GenerateValue
	private Long id;
	
	@Column(name = "id_number")
	private String idNumber;
}

注意1:fetch = FetchType.LAZY表示拦截
注意2:cascade = CascadeType.ALL表示无论你对User表做什么操作,JPA都会自动帮你关联操作身份证表;
如果改成PERSIST,cascade = CascadeType.PERSIST那么只有在你保存User表时,JPA才会帮你关联操作身份证表,其他时候比如像修改这样的操作,JPA就不会自动帮修改身份证表;
注意3:关联类型有几种
第一种:ALL:表示所有操作都会关联操作
第二种:PERSIST:表示只有在保存时,才会关联操作
第三种:REMOVE:只有在删除时,才会关联操作
第四种:MERGE:只有在修改时,才会关联操作
一般后面几种我们都不用,我们都用ALL
注意4:以上两个具有关联关系的实体类创建好后,我们再运行一个空的测试方法,就能正在数据库中创建好这两张表,并且它们之间的关联关系也弄好了;

1.1 测试

你要测试的话,首先你这是两张表,一张t_user表,一张t_idcard表,你想要JPA自动帮你进行关联操作,你除了要写userRepository,你还需要写idcardRepository,没有idcardRepository,那么连操作身份证表的mapper接口都没有;

@Repository
public interace userRepository extends JpaRepository<User,Long>{
}
@Repository
public interace idcardRepositoryextends JpaRepository<IDCard,Long>{
}

2. 一对多关系 @OneToMany

以作者跟文章为例,一个作者有多个文章,一个文章只能有一个作者;
所以我们在作者类中,就要用@OneToMany,在文章类中就要用@ManyToOne;
注意:在JPA中,一对多的双向关系是由多端来进行维护的,也就是由文章端来进行维护,也就是说由Article文章端来负责增删改查,你只能对文章表进行增删改查,然后JPA再关联操作作者表;你不能直接对作者端增删改查;这就是JPA中一对多关系只能由多端来进行维护的意思;

所以说:我们在一端这边,Author这边,我们使用@OneToMany,并且里面加上mappedBy = “author”表示Author表现在是被维护的一端;

Author类:

@Entity
@Table(name = "t_author")
public class Author{
	@Id
	@GenerateValue
	private Long id;
	
	@Column(name = "user_name")
	private String userName;
	
	@OneToMany(mappedBy = "author", cascade = CascadeType.All, fetch = FetchType.LAZY)
	private List<Article> articleList;
}

注意1:mappedBy = "author"中的author是Article类中的author属性,表示这是被author属性维护的;
注意2:cascade = CascadeType.All表示我对作者的任何操作,JPA都会关联操作文章;

Article类:

@Entity
@Table(name = "t_article")
public class Article{
	@Id
	@GenerateValue
	private Long id;
	
	@Lob  //加了这个注解,就表示这是大文本数据类型,对应Mysql中的Long Text类型;
	@Basic(fetch = FetchType.LAZY)
	@Column(nullable = false)
	private String context;

	@Column(nullable = false ,name = "title")
	private String title;
	
	@JoinToOne(name = "author_id")
	@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},optional = false)
	private Author author;
}

注意:optional = false表示author不能为空

2.1 一对多注意的点:

1.懒加载的问题:

我们先通过id将author查出来,看能不能一并把author下面的所有文章articleList也查出来;

authorRepository.findById(1L).ifPresent(System.out::println)

当我们执行后,我们会发现报了一个懒加载异常
在这里插入图片描述
解决办法:把Author中的懒加载改成实时加载。
修改前:
在这里插入图片描述
修改后:

@OneToMany(mappedBy = "author", cascade = CascadeType.All, fetch = FetchType.EAGER)
private List<Article> articleList;
2. 栈内存溢出的问题

当我们上面把懒加载改成实时加载后,我们再次运行这句代码;

authorRepository.findById(1L).ifPresent(System.out::println)

发现报了一个占内存溢出的异常:这是为什么呢?
这是因为这里产生了循环遍历的问题:上面的authorRepository.findById(1L)代码会查询出author下面的articleList,并且点儿了ifPresent(System.out::println)这句代码,它就会一直调用articleList里每一个article的toString方法,但是又由于article又有一个author属性,它就会调用author的toString,调用author的toString,又会导致要调用article的toString,导致陷入无限循环;
在这里插入图片描述
解决办法:破坏掉它们任意一个toString方法;
比如我现在破坏掉Article的toString,使用@ToString(exclude = {“author”})来排除author的转换;
意思就是,当我们在调用Article的toString时,不会将author属性也算进去;

@Entity
@Table(name = "t_article")
@Data
@ToString(exclude = {"author"}) //.........................看这里
public class Article{
	@Id
	@GenerateValue
	private Long id;
	
	@Lob  //加了这个注解,就表示这是大文本数据类型,对应Mysql中的Long Text类型;
	@Basic(fetch = FetchType.LAZY)
	@Column(nullable = false)
	private String context;

	@Column(nullable = false ,name = "title")
	private String title;
	
	@JoinToOne(name = "author_id")
	@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH},optional = false)
	private Author author;
}

3. 多对多关系 @ManyToMany

比如用户跟权限的关系,用户跟权限之间就是多对多关系;
注意1:多对多关系中,我们一般不会像一对多,多对一那样设置CascadeType级联操作,因为多对多已经有中间表了;
注意2:一对多,多对一中,由多方来做维护端,但是在多对多中,可以指定任意一方为维护端;

以用户类跟权限类举例:
User类:

@Entity
@Data
@Table(name = "t_user")
public class User{
	@Id
	@GenerateValue
	private Long id;
	
	@Column(name = "user_name", nullable = false, length = 20, unique = true)
	private String userName;

	@Column(length = 100)
	private String password;
	
	@JoinTable(name = "user_authority", joinColumns = @joinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "authority_id"))
	@ManyToMany
	private List<Authority> authorityList;
}

注意1:unique = true表示当前属性是唯一不可重复的;
注意2:
@JoinTable中name = "user_authority"表示指定中间表的名字为user_authority,
joinColumns = @joinColumn(name = “user_id”)表示t_user表在中间表中使用user_id进行表示,
inverseJoinColumns = @JoinColumn(name = “authority_id”))表示被转换的列,也就是authority表在中间表中用authority_id进行表示;

Authority类:

@Entity
@Data
@Table(name = t_authority)
public class Authority{
	@Id
	@GenerateValue
	private Long id;
	
	@Column(nullable = false)
	private String name;

	@ManyToMany(mappedBy = "authorityList")  //意思就是Authority类是根据User类中authorityList进行的关系的维护
	private List<User> userList;
}

注意:这样写好后,我们再任意运行一个空的测试方法,就能在数据库中创建出对应的表,并且生成对应的中间表;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值