文章目录
Spring Data JPA
一个持久层框架,根据实体类自动生成表 (注意库仍旧自己创建)、默认提供常用dao方法、按照命名规则自动生成dao方法、自定义dao方法等,极大提升开发效率。
springboot集成新框架环境往往很容易:引入依赖,编写配置、[启用]、代码编写。
引入依赖
jpa核心依赖:
<!-- jpa依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
附属必须依赖:使用mysql要引入驱动依赖。
<!-- 配置mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 配置druid链接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
当然还有一些非必须依赖,如lombok,这里就不帖了。
添加配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm?allowMultiQueries=true&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: root
jpa:
hibernate:
# create 启动时删数据库中的表,然后创建,退出时不删除数据表
# create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
# update 如果启动时表格式不一致则更新表,原有数据保留
# validate 项目启动表结构进行校验 如果不一致则报错
ddl-auto: update # 第一次建表create 后面用update,要不然每次重启都会新建表
show-sql: true #控制台显示sql语句
特别注意:ddl-auto的值在表没创建时,先改为create运行创建表,创建完成后,改为update重新启动即可(如果update出错,可以将这条配置注释掉即可)。
此时jpa的基础环境已经完成了,只剩下代码的编写。
代码编写
主要和mybatis不同的就是,不用自动创建表(实体类差别),dao不用自己写.
1.实体类
jpa会根据实体类的信息创建表,下面只是示范内容,详细注解不止这些。
@Data
@Entity //声明实体类
@Table(name = "jpa_test") //声明表名
public class User{
@Id //主键
@GeneratedValue(strategy = GenerationType.IDENTITY) //自增
private Integer id;
@Column(name = "name",length = 20) //设置字段,不设置的字段名默认为属性名(驼峰转蛇型)
private String name;
private String password;
private String sex;
}
注解的name属性值可能会有红色下划线:
1.提示Assign data sources:对项目没有影响,出现报错的原因是找不到对应的表,解决办法是让idea连接数据库,然后鼠标放于报错点,在弹窗设置数据源,如果数据库没有实体类对应表,将yml改为create运行一次即可。
2.其它错误:可能是注解包导错了,正确包为:javax.persistence。
2.Controller
常规的控制层
@RestController
public class JpaController {
@Autowired
private UserService userService;
@PostMapping("/add")
public Map<String,String> addUser(){
User user=new User();
user.setName("张三");
user.setPassword("123456");
user.setSex("男");
userService.insertUser(user);
Map<String,String> map=new HashMap<>();
map.put("msg","操作成功");
return map;
}
}
3.Service与ServiceImpl
常规的Service层接口和ServiceImp实现。
业务层接口类
public interface UserService {
//查询全部
List<User> findUserList();
//查询一条
User findUserById(int id);
//添加
void insertUser(User user);
//删除
void deleteUser(int id);
//修改
void updateUser(User user);
}
业务层实现类:
查询一条数据时没有直接使用User而是使用Optional< User >,这是由于Dao层直接使用了默认的方法。
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findUserList() {
return userMapper.findAll();
}
@Override
public User findUserById(int id) {
//Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
Optional<User> uo = userMapper.findById(id);
return uo.orElse(null);
}
@Override
public void insertUser(User user) {
userMapper.save(user);
}
@Override
public void deleteUser(int id) {
userMapper.deleteById(id);
}
@Override
public void updateUser(User user) {
User emp=userMapper.findById(user.getId()).orElse(null);
assert emp != null;
BeanUtils.copyProperties(user,emp); //属性值拷贝
userMapper.save(emp);
}
}
修改和添加都是save方法,修改时对象id有值,添加时id无值。
4.Dao
继承接口JpaRepository,它默认的提供了一些常见dao方法。
@Repository
public interface UserMapper extends JpaRepository<User,Integer> {
//约束1为实体类类型、约束2为主键类型
}
至此基础的crud就完成了,
附录:三种实现Dao功能方式
1.继承接口,使用默认接口+实现
接口 | 作用 |
---|---|
CrudRepository | 提供默认增删改查方法 |
PagingAndSortingRepository | CRUD方法+分页、排序 |
JpaRepository | 针对关系型数据库优化 |
2.根据接口命名规则默认生成实现
默认提供了常见方法,但仍可以根据命名规则自动生成方法。
此表内容来源于官网:https://docs.spring.io/spring-data/jpa/docs/2.4.3/reference/html/#jpa.query-methods.query-creation
关键词 | 示例 | JPQL片段 |
---|---|---|
Distinct | findDistinctByLastnameAndFirstname | select distinct … where x.lastname = ?1 and x.firstname = ?2 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is, Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull, Null | findByAge(Is)Null | … where x.age is null |
IsNotNull, NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1(参数附后%) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1(参数加前缀%) |
Containing | findByFirstnameContaining | … where x.firstname like ?1(参数绑定在中%) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |
根据命名规则默认实现接口的操作如下:
3.自定义接口+实现(类似MyBatis)
使用注解方式 @Query
使用 @Query()注解来生成sql语句,注意此注解默认value属性值与myBatis有点不同,它使用的是JPQL。
如果想使value值为原生SQL,则添加属性:nativeQuery = true 即可。
表名映射:可以直接使用表对应类名,如果想用表名:#{#entityName}
参数映射:?n表示第n个参数、:参数名(参数可用@Param指定)
#{#entityName}:SPEL表达式,实体类使用了@Entity后,它的值为实体类名,如果@Entity的name属性有值,则它的值为该name值。
@Modifying:标记仅映射参数的方法。
@Transactional:开启事务,并将只读改为非只读。
@Repository
//约束1为实体类、约束2为主键
public interface UserMapper extends JpaRepository<User,Integer> {
//添加:使用了原生sql
@Transactional//开启事务为非只读
@Modifying
@Query(value = "insert into jpa_test(name,password,sex) values(:#{#user.name} ,:#{#user.password},:#{#user.sex}) ",nativeQuery = true)
void addUser(@Param("user") User user);
//删除
@Transactional(timeout = 10)
@Modifying
@Query("delete from User where id=:id")
void deleteUserById(@Param("id") Integer id);
//修改
@Transactional(timeout = 10)
@Modifying
@Query("update User u set u.name=:#{#user.name},u.password=:#{#user.password},u.sex=:#{#user.sex} where u.id=:#{#user.id}")
void updateUser(@Param("user")User user);
//查询一条
@Query("select u from User u where u.id=?1 ")
User findUserById(Integer id);
//查询全部
@Query("select u from User u")
List<User> findAllUser();
}
对象属性的绑定:使用 @Param(映射名) 注解 + :#{#映射名.属性}
多表关联
JPA中一般只需要创建关联性即可,默认方法会自动关联查询。
1.一对一关联
两张表a、b,a的每条对应着b最多一条数据。
jpa实现如下:
/**
* 表A
*/
@Entity
@Table(name = "a")
public class A{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne(cascade = {CascadeType.ALL})//一对一关系,级联删除
@JoinColumn(name="b",referencedColumnName = "id")//关联 b的id字段
private B b;
}
/**
* 表B
*/
@Entity
@Table(name = "b")
public class B{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
上面是A级联B,即可以通过A查到B,如果想通过B查到A则需要为B添加级联属性。
2.一对多、多对一
一对多: 两张表A、B,A的一条记录对应B的多条记录,B每条只能对应1个A。
A对B的关系为一对多;B对 A的关系为多对一。
JPA实现:
球员与位置的关系,一个球员只有一个位置,一个位置可以有多个球员。
/**
* 球员表
*/
@Entity//球员表
@Table(name = "sportmans")
public class SportMan implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String sportManName;
@ManyToOne(cascade = {CascadeType.MERGE,CascadeType.PERSIST})
@JoinColumn(name="duty") //库中添加的外键字段
private Duty duty;
}
/**
* 位置表
*/
@Data
@Entity
@Table(name = "dutys")
public class Duty implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String dutyName;
@JsonIgnore//不反向查询
//级联保存、更新、删除,删除时会删除所有球员
@OneToMany(mappedBy = "duty",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
private List<SportMan> sportManList;
}
不使用@JsonIgnore注解时,查询球员,球员里关联出位置,位置反向关联球员,会无限递归查询,因此添加此注解,防止此字段被查出来时自动回查。
3.多对多
两张表A、B,一条A记录对应多条B,一条B记录对应多条A。
JPA实现:
/**
* 表A
*/
@Entity
@Table(name = "a")
public class A{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(cascade = {CascadeType.ALL})
@JoinColumn(name="b",referencedColumnName = "id")//关联 b的id字段
private B b;
}
/**
* 表B
*/
@Entity
@Table(name = "b")
public class B{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(cascade = {CascadeType.ALL})
@JoinColumn(name="a",referencedColumnName = "id")//关联 a的id字段
private A a;
}