数据库访问中间件
SpringBoot整合MyBatis
一、配置
MyBatis的配置
设置mybatis的配置文件路径,默认是在resources的目录
mybatis.config-location=classpath:mybatis/mybatis-config.xml
设置mybatis下面的mapper映射文件路径,默认是在resources的目录
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
设置项目中实体类包路径;
mybatis.type-aliases-package=com.study.springbootmybatis.entity
设置缓存失效
spring.thymeleaf.cache=false
展示sql语句
logging.level.com.study.springbootmybatis.repository.UsersDao=debug
测试
@SpringBootTest
class SpringbootMybatisApplicationTests {
@Resource
UsersDao usersDao;
@Test
void contextLoads() {
System.out.println(usersDao.findAll());
}
}
注解
public interface UsersDao {
非注解,在mapper中配置
List<Users> findAll();
Users findById(int id);
注解
@Select("select id,username,password,name from users where name like #{name}")
Users findByName(String name);
}
零配置
在properties中删除相关xml配置,删除实体类路径。
在资源中删除xml文件
在dao接口中删除非注解方法。
Springboot结合rest和mybatis完成业务操作
这里是演示一对多中的多
@Select("select * from Product where uid=#{uid}")
Product findByUid(int uid);
演示一对多中的1,这里是根据id来查users对象,那么我们可以结合ProductRepository来一次性把对应的product也查出来
property指的是在Users实体类中一对多中的多的属性名,column代表的是把users表中的那个字段作为后面查询的条件,many指的是查询是一对多还是一对一
@Select("select * from Users where id=#{id}")
@Results({
@Result(
property = "products",column = "id",many = @Many(select = "com.study.springbootmybatis.repository.ProductRepository.findByUid")
)
})
Users findUsersById(int id);
Spring Data Jpa 的简介和快速入门
一、Spring Data
简介
Spring Data是为了简化构建基于Spring框架应用的数据访问技术。包括对关系数据库、非关系数据库、Map-Reduce框架、云数据服务等访问支持。它为我们提供使用统一的API标准来对数据访问层进行操作,这套标准包含了CRUD(创建、获取、更新、删除)、查询、排序和分页的相关操作。
特点
- 支持对象关系映射:
- 具备ORM框架的对象关系映射功能
- 统一的Repository接口
- Repository<T,ID extends Serializable>: 统一接口
- CrudRepository<T,ID extends Serializable> 基本CRUD操作
- PagingAndSortingRepository<T,ID> extends Serializable>:基本CRUD及分页
- 统一的数据访问模板类xxxTemplate
- 如:MongoTemplate、RedisTempIate等
二、JPA
简介
- Jpa是 java持久化规范,它为Java开发人员提供一种对象/关联映射工具来管理Java应用中的关系数据。它的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在Hibernate、TopLink、JDO等ORM框架各自为营的局面
- Jpa是在充分吸收了现有的Hibernate、TopLink、JDO等ORM框架的基础上发展而来的,具有易于使用、伸缩性强等优点。
- JPA是一套规范,不是一套产品,那么像Hibernate、TopLink、JDO它们是一套产品,如果说这些产品实现了这个JPA规范,那么我们就可以称他们为JPA的实现产品。
注:jdbc是数据访问规范
Spring Data 与 JPA 的关系
Spring Data JPA 工作原理
快速入门
jpa配置
spring:
jpa:
show-sql: true //支持sql输出
database: mysql //数据库类型
hibernate:
ddl-auto: update //自动生成开启,让表数据会自动跟随entity类的变化而变化
properties:
hibernate:
format_sql: true //format一下SQL进行输出
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
repository
public interface UserRepository extends JpaRepository<TUser, Integer> {
TUser findTuserByUsernameLike(String username);
}
Spring Data JPA 的基本使用
一、基于单表的SQL操作
单表SQL操作-使用关键字拼凑方法
关键词 | 样例 | JPQL代码段 |
---|---|---|
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 | findByAgeIsNull | … 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.firstame) = UPPER(?1) |
示例
1、查询出年龄小于等于22岁的人;
List<Person> findAllByPageIsLessThanEqual(Integer age);
2、查询出年龄在20-22岁之间并且性别是男的人
List<Person> findAllByPageBetweenAndPsexEquals(Integer lowage,Integer highage,String sex);
3、查询出已经结婚并且性别是男的人
List<Person> findAllByGetmarriedIsTrueAndPsexEquals(String psex);
- 实体类属性名不要出现is×xx、get)(xx的名称,会导致关键字拼凑出错
- 实体类属性名中间只要出现了大写字母,就会导致数据库的字段名有下划线隔开,比如你使
- 用isMarried属性名,那么实体类的字段名就会变成is一married,这样容易导致找不到值
- 属性名类型是b[ean类型的在某些数据库中会变成bit(1)类型,其中0为false,1为true
实体类:
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Person {
@Id
@GenericGenerator(name = "myuuid",strategy = "uuid") 主键生成策略
@GeneratedValue(generator = "myuuid")
private String pid;
@Column(unique = true)
private String pname;
@Column
private String psex;
@Column
private Integer page;
@Column
private boolean getmarried;
}
关键拼凑字无法解决的问题
- 实体类的是属性名与表的字段名无法映射,导致关键字找不到
- CRUD操作方式比较另类或者是你不想用关键字的写法
- 涉及到了多表操作
使用注解手写sql语句
- 使用SQL语句来书写SQL
- 使用HQL语句来书写SQL
相关操作
- 按照pname来模糊删除
- 查询出年龄是20.22岁的并且性别是女的人
- 使用spe[表达式来演示用户的登陆
注意事项
- 如果是删改操作,需要加@Modifying和@Transactional注解
- 如果是SQL语句,请在@Query注解上加上NativeQuery=true的属性
- 传参的方式有两种:
- 使用?+数字的方式,数字从1开始,代表第几个参数
- 使用:+参数名的方式,这种方式最好配合@Param注解一起使用
示例
@Transactional
@Modifying
/示例1 @Query(value = "delete from Person where pname like ?1") 1代表匹配第一个参数
void deleteByName(String pname);/
或者这样↓ 推荐使用这种
/示例2 @Query(value = "delete from Person where pname like :pname")
void deleteByName(@Param("pname")String pname); /
示例3 @Query(value = "delete from Person where pname like %:pname%")
void deleteByName(@Param("pname")String pname);
默认是HQL语句,如果打sql语句需要多一个变量
/@Query(value = "select * from person where page between 20 and 22 and psex='女',",nativeQuery = true)/
@Query(value = "select p from Person p where p.page between 20 and 22 and p.psex='女'")
List<Person> findPerson();
使用SPEL表达式来完成person表的修改操作
@Modifying
@Transactional
@Query(value = "update person set pname=:#{#person.pname},psex=:#{#person.psex},page=:#{#person.page} where pid=:#{#person.pid}",nativeQuery = true)
void updatePerson(@Param("person")Person person);
二、JPA逆向工程及多表SQL操作
- 使用数据接口
- 构建一个数据接口,里面的抽象方法就是SQL语句的查询结果的字段对应的getXXX的抽象方法。
- 使用集合 (推荐)
- 直接使用List/Map等集合嵌套的方式来获取到接收数据
- 使用VO(view object)
- 单独构建一个跟页面展示数据对应的VO的实体类来接接收数据
利用idea右侧数据库工具连接上数据库
利用Jpa生成实体类
- project structure - > Modules -> 点击“+” -> 选择Jpa, -> 点击右侧“+” -> 选择第一个persistence.xml。-> 点击ok
- 在界面左侧出现persistence 标题卡 选择唯一的那个项目,右键选择Generate persistence Mapping -> 选择By DataSource Schema ,选择数据库,选择实体类生成所在的包。为想要生成table打上勾,实体类前缀 后缀为空。最后选择yes。
示例
联表查询-根据书名来查该书籍的拥有者
@Query(value = "select p from Person p inner join Book b on p.pid=b.pid where b.bname=:bname")
Person findPersonByBname(@Param("bname") String bname);
联表查询-根据用户id来查询person和book (通过数据接口的方式)
@Query(value = "select p.pid as pid, p.pname as pname, p.psex as psex, p.getmarried as getmarried," +
"b.bid as bid,b.bname as bname, b.bprice as bprice from Person p inner join Book b on p.pid =b.pid " +
"where p.pid=:pid")
List<PersonInfo> findAllInfo(@Param("pid") String pid);
联表查询-根据用户id来查询person和book (通过简单集合的方式)
@Query(value = "select p.pid as pid, p.pname as pname, p.psex as psex, p.getmarried as getmarried," +
"b.bid as bid,b.bname as bname, b.bprice as bprice from Person p inner join Book b on p.pid =b.pid " +
"where p.pid=:pid")
List<Object> findAllInfo1(@Param("pid") String pid);
联表查询-根据用户id来查询person和book (通过嵌套集合的方式)
@Query(value = "select p.pid as pid, p.pname as pname, p.psex as psex, p.getmarried as getmarried," +
"b.bid as bid,b.bname as bname, b.bprice as bprice from Person p inner join Book b on p.pid =b.pid " +
"where p.pid=:pid")
List<Map<String, Object>> findAllInfo2(@Param("pid") String pid);
上述示例用到的数据接口
public interface PersonInfo { //数据接口
String getPid();
String getPname();
String getPsex();
String getPage();
String getGetmarried();
Integer getBid();
String getBname();
double getBprice();
}
测试
数据接口的方式
List<PersonInfo> allInfo = personRepository.findAllInfo("40280981727e7c0401727e7c11210002");
for (PersonInfo info : allInfo) {
System.out.println(info.getPid()+","+ info.getPname() +","+ info.getPsex() +","+ info.getPage() +
","+ info.getGetmarried() +","+ info.getBid() +","+ info.getBname() +","+ info.getBprice() );
}
简单集合的方式
List<Object> allInfo1 = personRepository.findAllInfo1("40280981727e7c0401727e7c11210002");
Object[] o =(Object[]) allInfo1.get(0);
System.out.println(Arrays.toString(o));/
嵌套集合的方式 List<Map>
System.out.println(personRepository.findAllInfo2("40280981727e7c0401727e7c11210002"));
注: 多表SQL操作注意事项
- 使用数据接口的方式来接受查询的字段时要注意,必须要为要查询的字段名起别名,否则会无法取到
三、 QueryDSL的简单介绍
简介
- QueryDSL仅仅时一个通用的查询框架,专注于通过JavaAPI构建类型安全的Sql查询,也可以说QueryDSL是基于各种ORM框架以及Sql之上的一个通用的查询框架,QueryDSL的查询,类似于SQL查询,很全面只不过一个是用SQL一个是用代码来代替SQL
建议
- 建议 单表乃至简单的多表操作,都不推荐使用QueryDSL‘,使用JPA自带API简介又效率,但是涉及太复杂的查询,推荐使用QueryDSL
QueryDSL官网
操作总结:
Springboot中的分页
一、使用JPA自带的Pageable接口分页
Pageable
简介
Pageable分页是SpringDataJPA默认提供的分页功能,直接继承JpaRepository就获得了该分页功能;
分页模型
First | Previous | 1 | 2 | 3 | 4 | 5 | 6 | Next | Last |
---|
- 获取元素总数;
- 获取总页数;
- 获取当前页数;
- 获取当前页的内容;
- 是否有上一页、上一页;
- 是否有下一页、下一页的内容;
- 首页、尾页的内容;
API
JpaRepository 接口继承了 PagingAndSortingRepository接口
PagingAndSortingRepository接口有findAll方法来获取Page对象,但是需要Pageable接口作为方法参数传入。
findAll(Pageable pageable)
Returns a Page of entities meeting the paging restriction provided in the Pageable object.
static PageRequest of(int page, int size)
Creates a new unsorted PageRequest.
方法参数用接口Pageable的子类PageRequest。
示例
public String findAll(ModelMap modelMap, @RequestParam(defaultValue = "0", value = "pageNum") int pageNum){
------------------------------当前页数 每页元素数量 排序并且按照薪资倒叙
Pageable pageable = PageRequest.of(pageNum, 5, Sort.Direction.DESC, "sal");
Page<Emp> pages = empRepository.findA11(pageable);
当前页数
System.out.println("当前页数: "+pages.getNumber());
页数
System.out.println("总页数: " + pages.getTotalPages());
总数量
System.out.println("总数量: " + pages.getTotalE1ements());
所有数据
System.out.println("所有数据:" + pages·getContent());
是否有上一页
System.out.println("是否有上一页:" + pages·hasprevious());
是否有下一页
System.out.println("是否有下一页:" + pages·hasNext());
当前页的上一页
System.out.println("当前页的上一页:" + (pages.hasPrevious()? pages.previousPageable().getPageNumber():
pages.getPageable().getPageNumber()));
当前页的下一页
System.out.println("当前页的下一页:" + (pages.hasNext()?pages.nextPageable().getPageNumber():
pages.getPageable().getPageNumber()));
modelMap.addAttribute("pages", pages);
return "list";
二、使用Mybatis和PageHelper插件进行分页
配置
pagehelper.helperDia1ect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSq1
API
方法 | 含义 |
---|---|
Pagelnfo list | 结果集 |
Pagelnf0.pageNum | 当前页码 |
Pagelnf0.pageSize | 当前页面显示的数据条目 |
Pagelnfo.pages | 总页数 |
Pagelnfo.total | 数据的总条目数 |
Pagelnf0.prePage | 上一页 |
Pagelnf0.nextPage | 下一页 |
Pagelnf0.isFirstPage | 是否为第一页 |
Pagelnf0.isLastPage | 是否为最后一页 |
Pagelnf0.hasPreviousPage | 是否有上一页 |
PageHelper.hasNextPage | 是否有下一页 |
示例
public String findAll(ModelMap modelMap, @RequestParam(defaultValue = "0", value = "pageNum") int pageNum){
PageHelper.startPage(pageNum, 5);
List<Emp> emps = usersRepository.findAll();
PageInfo<Emp> pages = new PageInfo<>(emps);
modelMap.addAttribute("pages", pages);
return "list";
}
资料
三、两种分页的区别
Pageable
- JPA自带的分页方式,不需要添加额外的支持及配置;
- 页数是从0开始,从size-1为止;
- 单表、简单的多表操作分页排序使用它方便快捷;
PageHelper
- 第三方插件,需要添加对应的依赖以及进行相关配置;
- 页数是从1开始,从size为止;
- 只支持Mybatis(由于PageHeIper是根据离他最近的一个sql执行,而jpa刚好是自动生成sql,所以PageHelper最好用mybaties。如果必须用就jpa的话需要引入entitymanager + Query查询)
- 复杂的查询使用PageHelper可以让你更加方便;