常见 ORM 框架
**Mybatis(ibatis):**一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java 对象)映射成数据库中的记录。
Hibernate:一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,它将 POJO 与数据库表建立映射关系,是一个全自动的 ORM 框架,hibernate 可以自动生成 SQL 语句,自动执行,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。
Jpa:Java Persistence API 的简称,中文名 Java 持久层 API,是 JDK 5.0 注解或 XML 描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
一、Spring data JPA简介
Spring data JPA是Spring在ORM框架,以及JPA规范的基础上,封装的一套JPA应用框架,并提供了一整套的数据访问层解决方案。
二、Spring data JPA的功能
Spring data JPA的功能非常的强大,这里我们先跳过环境搭建这一步,来一睹Spring data JPA的“芳容”。
Spring data JPA提供给用户使用的,主要有以下几个接口:
- Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别
- CrudRepository:继承Repository,实现了一组CRUD相关的方法
- PagingAndSortingRepository:继承CrudRepository,实现了一组分页排序相关的方法
- JpaRepository:继承PagingAndSortingRepository,实现一组JPA规范相关的方法
- JpaSpecificationExecutor:比较特殊,不属于Repository体系,实现一组JPA Criteria查询相关的方法。
三、Spring Data JPA对事务的支持
默认情况下,Spring Data JPA 实现的方法都是使用事务的。针对查询类型的方法,其等价于 @Transactional(readOnly=true);增删改类型的方法,等价于 @Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。
如果用户觉得有必要,可以在接口方法上使用 @Transactional 显式指定事务属性,该值覆盖 Spring Data JPA 提供的默认值。同时,开发者也可以在业务层方法上使用 @Transactional 指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。
四、JPA自定义SQL
顾名思义,就是根据方法的名字,就能创建查询
只要继承了JpaRepository<Object,Integer>接口就可以实现自定义SQL,对于简单的查询使用这种查询是最好不过的了
Keyword | Sample | JPQL snippet |
---|---|---|
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 (parameter bound with appended % ) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended % ) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in% ) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> age) | … 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) |
五、JPA拥有哪些注解呢?
注意:JPA是可以根据实体类属性自动生成表的,只要加入下面几个注解
@Entity
@Table(name = “”)
@Column(name = “”)
注解 | 解释 |
---|---|
@Entity | 声明类为实体或表。 |
@Table | 声明表名。 |
@Id | 指定的类的属性,用于识别(一个表中的主键)。 |
@GeneratedValue | 指定如何标识属性可以被初始化,例如自动、手动、或从序列表中获得的值。 |
@Column | 指定持久属性栏属性。 |
@Transient | 指定的属性,它是不持久的,即:该值永远不会存储在数据库中。 |
@Basic | 指定非约束明确的各个字段。 |
@Embedded | 指定类或它的值是一个可嵌入的类的实例的实体的属性。 |
@SequenceGenerator | 指定在@GeneratedValue注解中指定的属性的值。它创建了一个序列。 |
@TableGenerator | 指定在@GeneratedValue批注指定属性的值发生器。它创造了的值生成的表。 |
@AccessType | 这种类型的注释用于设置访问类型。如果设置@AccessType(FIELD),则可以直接访问变量并且不需要getter和setter,但必须为public。如果设置@AccessType(PROPERTY),通过getter和setter方法访问Entity的变量。 |
@JoinColumn | 指定一个实体组织或实体的集合。这是用在多对一和一对多关联。 |
@UniqueConstraint | 指定的字段和用于主要或辅助表的唯一约束。 |
@ColumnResult | 参考使用select子句的SQL查询中的列名。 |
@ManyToMany | 定义了连接表之间的多对多一对多的关系。 |
@ManyToOne | 定义了连接表之间的多对一的关系。 |
@OneToMany | 定义了连接表之间存在一个一对多的关系。 |
@OneToOne | 定义了连接表之间有一个一对一的关系。 |
@NamedQueries | 指定命名查询的列表。 |
@NamedQuery | 指定使用静态名称的查询。 |
了解了注解之后我们来看看如何使用吧
六、SQL能力
1. JPA有两种SQL查询方式
因为接口继承了JpaRepository<Object,Integer>接口,所以接口可以实现里面的所有方法
JPA的查询语言是面向对象而非面向数据库的,他以面向对象的自然语法构造查询语句,可以看成是Hibernate HQL的等价物。JPA定义了独特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一种扩展,它是针对实体的一种查询语言,操作对象是实体,而不是关系数据库的表,而且能够支持批量更新和修改、JOIN、GROUP BY、Having 等通常只有SQL才能够提供的高级查询特性,甚至还能够支持子查询。
原生SQL
原生SQL,顾名思义就是数据库原原本本的SQL语句,可以把SQL语句复制出来到数据库直接运行
/**
* 获取用户信息
* nativeQuery = true:使用sql查询
* nativeQuery = false:使用jpql查询,默认就是false
*/
@Query(value = "select * from users where username=?", nativeQuery = true)
Users findByUsername(String username);
JPQL查询
JPQL查询使用的是实体类属性
@Query(value = "select u.id,u.name from users u where u.username=?")
Users findByUsername(String username);
注意:使用如下SQL需要定义实体类构造函数,并且参数顺序一致
@Query("select new com.example.demo.entity.TestDemo(t.id,t.username,t.testTime) from TestDemo t")
List<TestDemo> testAll();
2. 传参方式
下表索引传参
@Query ( "select u from users u where id>?1 and username like %?2%" )
List <User > selectUserByParam ( Long id , String name );
@param 命名参数传参
@Query ( "select u from users u where id>:id and username like :name" )
List <User > selectUserByParam ( @param('id') Long id , @param('name') String name );
3. 动态SQL
是不是跟Mybatis的动态SQL有点相识,只不过这里是判断是否为空,为空了就不会往下走了,不为空就执行SQL将参数传进去进行查询
@Query("select t from TestDemo t where ((?1 is null or ?1 = '') or (?2 is null or ?2 = '') or (t.testTime between ?1 and ?2)) ")
Page<TestDemo> testAll1(String startTime, String endTime, Pageable pageable);
4. 两表联合分页查询
注意JPA原生SQL和Pageable配合会报错
JPA原生SQL和Pageable配合报错解决:https://blog.csdn.net/ITKidKid/article/details/130167244
这里只是做个示例,随便写的,具体根据实际情况开发
数据访问层 testRepository 接口
@Query("select new com.example.demo.vo.UserAddressVo(u.id,a.province,a.city,a.area) from User u inner join Address a on u.id = a.userId")
Page<UserAddressVo> pageAll(Pageable pageable);
业务逻辑层 testServiceImpl 接口实现类
testRepository.pageAll(PageRequest.of(page - 1, limit, Sort.Direction.DESC, "createDate"));
七、项目实战
1. 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<scope>compile</scope>
</dependency>
2. 生成代码
JPA代码生成:https://www.cnblogs.com/cl-rr/p/10397107.html
可以使用eazycode代码生成器生成代码,也可以使用我上面的这个链接,不过有些地方需要补全
3. 代码编写
控制层
/**
* (TestDemo)表控制层
*
* @author makejava
* @since 2023-04-26 14:32:35
*/
@RestController
@RequestMapping("/testDemo")
public class TestDemoController {
@GetMapping("/testAll")
public RespBean testAll(@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime) {
return RespBean.success("成功",testDemoService.testAll(startTime,endTime));
}
}
业务逻辑层
接口方法
public interface TestDemoService {
List<TestDemo> testAll(String startTime, String endTime);
}
接口方法实现
@Service
public class TestDemoServiceImpl implements TestDemoService {
@Autowired
private TestDemoRepository rep;
@Override
public List<TestDemo> testAll(String startTime, String endTime) {
return rep.testAll(startTime,endTime);
}
}
业务访问层
@Repository
public interface TestDemoRepository extends JpaRepository<TestDemo, Integer> {
@Query("select new com.example.demo.entity.TestDemo(t.id,t.username,t.testTime) from TestDemo t where ((?1 is null or ?1 = '') or (?2 is null or ?2 = '') or (t.testTime between ?1 and ?2)) ")
List<TestDemo> testAll(String startTime, String endTime);
}
看下结果:
Java全局日期类型转换配置:https://blog.csdn.net/ITKidKid/article/details/130421992
感谢:https://blog.csdn.net/wujiaqi0921/article/details/78789087
https://www.cnblogs.com/cl-rr/p/10397107.html