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