SpringBoot--实战开发--整合Spring Data
一、SpringData简介
Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷。
- Spring Data JPA能干什么
可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。 - Spring Data JPA 有什么
主要来看看Spring Data JPA提供的接口,也是Spring Data JPA的核心概念:
1:Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。
2:CrudRepository :是Repository的子接口,提供CRUD的功能
3:PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能
4:JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等。
5:JpaSpecificationExecutor:用来做负责查询的接口
6:Specification:是Spring Data JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件即可 - 特征
1)强大的存储库和自定义对象映射抽象
2)从存储库方法名称中进行动态查询导出
3)实现域基类提供基本属性
4)支持透明审核(创建,最后更改)
5)集成自定义存储库代码的可能性
6)Easy Spring通过JavaConfig和自定义XML命名空间进行集成
7)与Spring MVC控制器进行高级集成
二、Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
三、application.properties配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/iotManager?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=1234
#自动建表
spring.jpa.hibernate.ddl-auto=update
#设置数据库方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#打印sql
spring.jpa.show-sql=true
Jpa的配置后 jpa.hibernate.ddl-auto= update,在其他低版本的SpringBoot中也有使用spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop 这种配置的,具体根据版本而定。该配置的主要作用是:自动创建、更新、验证数据库结构
1、create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因(一般只会在第一次创建时使用)
2、create-drop:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除
3、update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会
4、validate:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
四、创建实体
创建一个User类,配置好上面的信息后,启动项目,对应的数据库就会自动生成对应的表结构。@Table、@Entity、@Id等注解是jpa的相关知识。
@Table(name = "t_user")
@Entity
@Data
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 设为主键 唯一不能为空
* nullable 是否可以为空
* unique 唯一
* @GeneratedValue
* 就是为一个实体生成一个唯一标识的主键
* (JPA要求每一个实体Entity,必须有且只有一个主键)
* @GeneratedValue提供了主键的生成策略。
* 。@GeneratedValue注解有两个属性,分别是strategy和generator,
* 其中generator属性的值是一个字符串,默认为"",其声明了主键生成器的名称
* (对应于同名的主键生成器@SequenceGenerator和@TableGenerator)。
* JPA为开发人员提供了四种主键生成策略,其被定义在枚举类GenerationType中,
* 包括GenerationType.TABLE,GenerationType.SEQUENCE,
* GenerationType.IDENTITY和GenerationType.AUTO。
* 这里生成策略为自增长
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String userName;
@Column(nullable = false)
private String passWord;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = true, unique = true)
private String nickName;
@Column(nullable = false)
private String regTime;
五、创建Dao
方法的名称要遵循 findBy + 属性名(首字母大写) + 查询条件(首字母大写 Is Equals)
findByNameLike(String name)
findByName(String name)
findByNameAndAge(String name, Integer age)
findByNameOrAddress(String name) 等...
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
/**
* 根据年纪查询用户
* @param age
* @return
*/
User findByAge(Integer age);
/**
* 根据年纪和姓名查询
* @param name
* @param age
* @return
*/
User findByNameAndAge(String name, Integer age);
/**
* 对于复杂查询可以使用@Query 编写sql
* @param name
* @return
*/
@Query("from User u where u.name=:name")
User findUser(@Param("name") String name);
}
该Dao成继承了JpaRepository接口,指定了需要操作的实体对象和实体对象的主键类型,通过查看JpaRepository接口源码可以看到,里面已经封装了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,使用起来非常方便了,但是还是会存在一些复杂的sql,spring-data-jpa还提供了一个非常方便的方式,通过实体属性来命名方法,它会根据命名来创建sql查询相关数据,对应更加复杂的语句,还可以用直接写sql来完成。
继承了JpaRepository就相当于有了下面的数据访问操作方法:
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
List<T> findAll();
List<T> findAll(Sort var1);
List<T> findAll(Iterable<ID> var1);
<S extends T> List<S> save(Iterable<S> var1);
void flush();
<S extends T> S saveAndFlush(S var1);
void deleteInBatch(Iterable<T> var1);
void deleteAllInBatch();
T getOne(ID var1);
}
六、单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringdataApplicationTests {
@Autowired
private UserRepository userRepository;
/**
* 新增用户
* @throws Exception
*/
@Test
public void testAddUser() throws Exception {
User user = new User();
user.setName("zhangsan");
user.setAge(12);
userRepository.save(user);
User user2 = new User();
user2.setName("lishi");
user2.setAge(22);
userRepository.save(user2);
}
/**
* 删除用户(根据对象删除时,必须要有ID属性)
* @throws Exception
*/
@Test
public void testDelUser() throws Exception {
User user = new User();
user.setId(1L);
user.setName("zhangsan");
user.setAge(12);
userRepository.delete(user);
}
/**
* 修改用户信息
* @throws Exception
*/
@Test
public void testUpdUser() throws Exception {
User user = new User();
user.setId(2L);
user.setName("zhangsan11");
user.setAge(122);
userRepository.save(user);
}
/**
* 查询用户
* @throws Exception
*/
@Test
public void testQueryUser() throws Exception {
User user = userRepository.findByAge(22);
System.out.println(user.getName());
User user2 = userRepository.findByNameAndAge("lishi", 22);
System.out.println(user2.getName());
User user3 = userRepository.findUser("zhangsan11");
System.out.println(user3.getName());
}
/**
* 查询所有用户
* @throws Exception
*/
@Test
public void testQueryUserList() throws Exception {
List<User> list = userRepository.findAll();
for (User user : list) {
System.out.println(user.getName());
}
}
}
如果没有表,将会自动生成。
七、主要接口
CrudReposiroty : 继承了Repository
Crud主要是添加了对数据的增删改查的方法
PagingAndSortingRepository: 继承了CrudRepository
JPARepository: 继承了PagingAndSortingRepository接口
JpaSpecificationExecutor: 这个接口单独存在,没有继承以上说的接口
主要提供了多条件查询的支持,并且可以在查询中添加分页和排序。
因为这个接口单独存在,因此需要配合以上说的接口使用,如:
/**
* JpaSpecificationExecutor是单独存在的,需要配合这JpaRepository一起使用
*/
@Repository
public interface UserJpaSpecificationExecutor extends JpaSpecificationExecutor<User>, JpaRepository<User, Integer> {
}
八、JPA的查询方法
基于@Query注解的查询和更新:
/**
* SQL nativeQuery的值是true 执行的时候不用再转化
* @param name
* @return
*/
@Query(value = "SELECT * FROM table_user WHERE name = ?1", nativeQuery = true)
List<User> findByUsernameSQL(String name);
基于HQL:
/**
* 基于HQL
* @param name
* @param id
* @return
*/
@Query("Update User set name = ?1 WHERE id = ?2")
@Modifying
int updateNameAndId(String name, Integer id);
在JPA中有三种方式可以进行数据的查询(1,方法命名查询 2,@NamedQuery查询 3,@Query查询),
假设有一张表叫PERSON,字段:ID(INT),NAME(VARCHAR),AGE(INT),ADDRESS(VARCHAR).
实体类:id(integer),name(String),age(integer),address(String)
- 第一种:方法命名查询
1) 使用findBy,And关键字
public interface PersonRepository extends Repository<Person, Integer> {
/*
* 通过地址进行查询,参数为address,
* 相当于JPQL:select p from Person p where p.address=?1
* */
List<Person> findByAddress(String address);
/*
* 通过地址和名字进行查询,参数为name,address
* 相当于JPQL:select p from Person p where p.name=?1 and address=?2
* */
Person findByNameAndAddress(String name,String address);
}
从代码可以看出,使用findBy,And这样的关键字,其中的findBy可以用find,getBy,query,read来进行代替。
而And就相当于sql语句中的and。
2) 用关键字限制结果数量,用top和first来实现
*
*查询符合条件的前十条记录
*/
List<Person> findFirst10ByName(String name)
/*
*查询符合条件的前30条记录
*/
List<Person> findTop30ByName(String name);
- 第二种:@NamedQuery查询
Spring Data JPA 支持@NameQuery来定义查询方法,即一个名称映射一个查询语句(要在实体类上写,不是接口里写):
@Entity
@NamedQuery(name="Person.findByName",
query="select p from Person p where p.name=?1")
public class Person{
}
这样子就重新定义了findByName这个方法了。
如果要将多个方法都进行重新定义,可以使用@NameQueries标签,示例如下:
@Entity
@NamedQueries({
@NamedQuery(name="Person.findByName",
query="select p from Person p where p.name=?1"),
@NamedQuery(name = "Person.withNameAndAddressNamedQuery",
query = "select p from Person p where p.name=?1 and address=?2")
})
public class Person{
}
这个时候,接口里定义的findByName方法就是上面的方法了,不再是方法命名查询的方法了。
- 第三种:@Query查询
Spring Data JPA 支持@Query来定义查询方法,使用方法是将@Query写在接口的方法上面:
public interface PersonRepository extends Repository<Person, Integer> {
@Query("select p from Person p where p.name=?1 and p.address=?2")
Person withNameAndAddressQuery(String name,String address);
}
这里的参数是根据索引号来进行查询的。
当然我们也是可以根据名称来进行匹配,然后进行查询的,示例如下:
public interface PersonRepository extends Repository<Person, Integer> {
@Query("select p from Person p where p.name= :name and p.address= :address")
Person withNameAndAddressQuery(@Param("name")String name,@Param("address")String address);
}
Spring Data JPA支持使用@Modifying和@Query注解组合来进行更新查询,示例如下:
public interface PersonRepository extends Repository<Person, Integer> {
@Modifying
@Transcational
@Query("update Person p set p.name=?1 ")
int setName(String name);
}
int表示的是更新语句所影响的行数。
- 排序查询
userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,"id")));
- 分页查询
//分页查询全部,返回封装了分页信息,注意: 0为第一页,1为第2页
Page<User> pageInfo = userRepository.findAll(PageRequest.of(0, 3, Sort.Direction.ASC, "id"));
log.info("总页数" + pageInfo.getTotalPages());
log.info("页大小" + pageInfo.getSize());
log.info("当前页" + pageInfo.getPageable().getPageNumber());
log.info("总记录数" + pageInfo.getTotalElements());
//内容
List<User> userList = pageInfo.getContent();
- example查询
User user = new User();
user.setUsername("admin");
Example<User> example = Example.of(user);
List<User> list = userRepository.findAll(example);
log.info(list);
- getOne方法
需添加配置:
#延迟加载
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
运行:
# 只可读取存在的数据,不存在抛异常
User user=userRepository.getOne(1L);
- findOne方法
User user=new User();
user.setId(3L);
Example example=Example.of(user);
Optional<User> optionalUser=userRepository.findOne(example);
log.info(optionalUser.get().getUsername());
说明:如果不存在,会报:No value present异常
User user = new User();
user.setId(3L);
Example example = Example.of(user);
Optional<User> optionalUser = userRepository.findOne(example);
//判断是否存在
if (optionalUser.isPresent()) {
log.info(optionalUser.get().getUsername());
}
或:
//存在即返回, 无则提供默认值
User user = new User();
user.setId(id);
Example<User> userExample = Example.of(user);
return userRepository.findOne(userExample).orElse(null);
在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:
And --- 等价于SQL 中的and 关键字,比如findByUsernameAndPassword(String user, Striang pwd);
Or --- 等价于SQL 中的or 关键字,比如findByUsernameOrAddress(String user, String addr);
Between --- 等价于SQL 中的between 关键字,比如findBySalaryBetween(int max, int min);
LessThan --- 等价于SQL 中的"<",比如findBySalaryLessThan(int max);
lGreaterThan --- 等价于SQL 中的">",比如findBySalaryGreaterThan(int min);
IsNull --- 等价于SQL 中的"is null",比如findByUsernameIsNull();
IsNotNull --- 等价于SQL 中的"is not null",比如findByUsernameIsNotNull();
NotNull --- 与IsNotNull 等价;
Like --- 等价于SQL 中的"like",比如findByUsernameLike(String user);
NotLike --- 等价于SQL 中的"not like",比如findByUsernameNotLike(String user);
OrderBy --- 等价于SQL 中的"order by",比如findByUsernameOrderBySalaryAsc(String user);
Not --- 等价于SQL 中的"! =",比如findByUsernameNot(String user);
In --- 等价于SQL 中的"in",比如findByUsernameIn(Collection<String> userList) ,方法的参数可以
是Collection 类型,也可以是数组或者不定长参数;
NotIn --- 等价于SQL 中的"not in",比如findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是Collection 类型,也可以是数组或者不定长参数;