本文将介绍springboot中jpa各种详细使用方法,之前那篇写的太急了,水地太多了
依赖
gradle构建,使用的是mysql数据库和Jpa框架
plugins {
id 'org.springframework.boot' version '2.2.4.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'mysql:mysql-connector-java'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
配置文件
server:
port: 10000
spring:
application:
name: jpa-project
datasource:
url: jdbc:mysql://localhost:33306/jpa-project
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true #打印sql
启动注解
@EnableJpaAuditing
@EntityScan("com.ladyishenlong.jpaproject.model")
@EnableJpaRepositories("jpa")
@SpringBootApplication
public class JpaProjectApplication {
public static void main(String[] args) {
SpringApplication.run(JpaProjectApplication.class, args);
}
}
在项目入口的类里面需要添加如上代码中的这些注解
- @EnableJpaAuditing 是使用 @CreatedBy, @CreatedDate,@LastModifiedDate,@LastModifiedBy这四个注解所需要添加的
- @EntityScan 是表映射实体类所在包的路径,就是@Entity注解添加的实体类所在的包的路径
- @EnableJpaRepositories 就是继承了JpaRepository的接口所在的包的位置
用户名配置
@Configuration
public class UserAuditorAware implements AuditorAware {
@Override
public Optional getCurrentAuditor() {
//从security框架中获取用户信息
//SecurityContext ctx = SecurityContextHolder.getContext();
return Optional.of("配置用户名");
}
}
- 使用 @CreatedBy 和 @LastModifiedBy 时注入的值在这里可以配置
- 通常配合spring security框架获取用户登录信息
表说明
BaseTable
@Data
@MappedSuperclass
public class BaseTable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@CreatedDate
private Date createTime;
@CreatedBy
private String createBy;
@LastModifiedDate
private Date lastModifyTime;
@LastModifiedBy
private String lastModifyBy;
}
- @MappedSuperclass 表示该实体类并非是任何表的映射,只是用于书写公共字段,包括主键字段,表的映射实体类继承该类即可
- 子类需要添加 @EntityListeners(AuditingEntityListener.class) 且使用save()或者saveAll()方法才能使@CreatedDate,@CreatedBy,@LastModifiedDate, @LastModifiedBy 发挥作用
- @CreatedDate,@CreatedBy 在insert的时候会有值,但是update的时候会变成null,需要先查出来赋值才行
ProfessionTable
@Data
@Entity
@Table(name = "profession")
@EqualsAndHashCode(callSuper = true)
@EntityListeners(AuditingEntityListener.class)
public class ProfessionTable extends BaseTable {
private String proName;
}
- 这是学生专业信息表,继承BaseTable的字段基础上添加了proName
- 在新版本的idea中使用@Table注解映射表名会有红色的下划线,可以用idea自带的数据库连接工具连接数据库就能去除,当然不去除只要表名写对也没有什么问题
StudentTable
@Data
@Entity
@Table(name = "student")
@EqualsAndHashCode(callSuper = true)
@EntityListeners(AuditingEntityListener.class)
public class StudentTable extends BaseTable {
private String name;
@Column(name = "pro_id")
private int proId;
@OneToOne
@JoinColumn(name = "pro_id", referencedColumnName = "id",
insertable = false, updatable = false)
private ProfessionTable profession;
}
- 这个就是学生信息表,继承了BaseTable表的字段,再见了name字段和proId字段
- proId字段对应的是profession表的主键,在Jpa里面使用 @OneToOne和@JoinColumn来进行表关联
- @OneToOne用在一对一的关系,本文不再讲述一对多,多对多的表关联,因为过于复杂建议直接用原生sql来操作
- @JoinColumn用于注解两张表之间的关系,其中name是本表的关联字段,referencedColumnName对应的从表的关联字段insertable和updatable设置插入和更新是否联动,这里全部设置成false
- @OneToOne和@JoinColumn都是写在主表的注释上,可以从主表查从表,而不能从从表查主表
Jpa
public interface StudentJpa extends JpaRepository {
}
- 普通地继承JpaRepository类,这里只用StudentTable的JpaRepository
简易数据库操作
查询
@RestController
public class TestController {
@Autowired
private StudentJpa studentJpa;
@GetMapping("/")
public Object test() {
return studentJpa.findAll();
}
}
[
{
"id": 1,
"createTime": null,
"createBy": null,
"lastModifyTime": null,
"lastModifyBy": null,
"name": "藤丸立香",
"proId": 1,
"profession": {
"id": 1,
"createTime": null,
"createBy": null,
"lastModifyTime": null,
"lastModifyBy": null,
"proName": "人理修复"
}
},
{
"id": 2,
"createTime": "2020-01-27T03:45:29.000+0000",
"createBy": "我是用户名",
"lastModifyTime": "2020-01-27T03:45:29.000+0000",
"lastModifyBy": "我是用户名",
"name": "阿提拉",
"proId": 1,
"profession": {
"id": 1,
"createTime": null,
"createBy": null,
"lastModifyTime": null,
"lastModifyBy": null,
"proName": "人理修复"
}
}
]
- 这里可以看见关联表的数据是以实体类的方法放回的,并不是sql语句返回的拉平的格式,而且Hibernate生成打印出来的sql也是两句,并不是连表查询是一句搞定
插入,更新,删除
@Transactional
@GetMapping("/")
public Object test() {
StudentTable table=new StudentTable();
table.setName("搞事情");
studentJpa.save(table);
studentJpa.deleteAll();
return studentJpa.findAll();
}
- Jpa里面save()和saveAll()方法同时用于插入和更新,没有赋值的字段默认为null,如果更新的时候没有把原来的字段的值先查出来赋值也会变为null
自定义插入,更新,删除
JpaRepository中也可以自己写语句来进行插入或者更新,不过还是需要加上@Modifying注解
@Modifying
@Query("update StudentTable set name=:name where id=:id")
void updateName(@Param("name") String name, @Param("id") int id);
@Modifying
@Query(nativeQuery = true,value = "update student set name = :name where id=:id")
void updateName2(@Param("name") String name, @Param("id") int id);
- 第一种是hql也就是Jpa的写法,idea在书写的时候会有提示,第二种是原生sql的写法
- 这三种数据库操作需要加上@Transactional注解在事务中进行,不建议在controller中直接进行数据库操作,这里只是示例
- 大量的插入和更新不适合用jpa的方法进行操作,效率很低建议直接用jdbcTemplate来进行操作
- @Param必须添加来指定传入的参数在语句中的位置
复杂数据库操作
- 增改删在数据量大不的情况下使用起来都很简单,但是复杂的查询操作很麻烦,所以这里的复杂数据库操作主要是复杂的查询
- 这里的复杂操作都是指使用jpa的hql语句来查询,原生sql查询没有这些问题
查询部分字段
Jpa的方法都是默认查询全部字段,在实际业务中肯定不现实
@Query("select i.name as name from StudentTable i where i.id=:id")
StudentTable getNameById(@Param("id") int id);
- 首先想到的是用这种写法查询,但是执行失败,代码会报错
dto 查询
Jpa中想要查询部分字段的方法我找到的是使用dto
public interface StudentDto {
String getName();
}
@Query("select i.name as name from StudentTable i where i.id=:id")
StudentDto getNameById(@Param("id") int id);
如上代码所示,查询出来的就只有name值,且Hibernate生成的sql为:
select studenttab0_.name as col_0_0_ from student studenttab0_ where studenttab0_.id=?
- dto中字段命名必须与get该字段的table中的方法名一致,且hql语句中需要添加as否则可能出现赋值失败该字段返回null的可能
dto 连表查询
dto查询能够解决单表查询部分字段的问题,但是连表查询依旧有问题
public interface ProfessionDto {
String getProName();
}
public interface StudentDto {
String getName();
ProfessionDto getProfession();
}
@Query("select i.name as name ,i.profession as profession " +
"from StudentTable i where i.id=:id")
StudentDto getNameById(@Param("id") int id);
返回结果为:
{
"name": "藤丸立香",
"profession": {
"proName": "人理修复"
}
}
然而Hibernate生成sql为
select studenttab0_.name as col_0_0_,
studenttab0_.pro_id as col_1_0_,
profession1_.id as id1_0_,
profession1_.create_by as create_b2_0_,
profession1_.create_time as create_t3_0_,
profession1_.last_modify_by as last_mod4_0_,
profession1_.last_modify_time as last_mod5_0_,
profession1_.pro_name as pro_name6_0_
from student studenttab0_
inner join profession profession1_ on studenttab0_.pro_id = profession1_.id
where studenttab0_.id = ?
- 主表查询的字段是部分的但是从表还是查了所有字段
- 暂时没有找到解决查询从表部分字段的办法,所以复杂的连表查询建议使用原生sql语句
复杂查询条件
- 这种查询我就不加累述了,Specification可以做到这一点,有兴趣的自行查询;但是Specification使用之后却又无法查询部分字段了,就显得很鸡肋,当然也可能是我没到方法
- 复杂条件的查询主要是用于例如查询时候有这个参数就生效,没有就不生效的情况使用,用or语句可以做到这一点,但是降低了效率
执行原生sql
jpa执行原生sql
@Modifying
@Query(nativeQuery = true, value = "update student set name = :name where id=:id")
void updateName2(@Param("name") String name, @Param("id") int id);
@Query(nativeQuery = true,
value = "select * from student where id = :id ")
List
- 在Jpa中执行原生sql只要nativeQuery属性设为true即可,不过查询语句的接收类型最好设置成 List
- 不过在JpaRepository下执行原生sql依旧不能满足部分复杂查询条件的使使用,就需要用到jdbcTemplate
NamedParameterJdbcTemplate
查询
- 推荐使用NamedParameterJdbcTemplate而不是直接用JdbcTemplate,根据参数名传入参数
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
- @Transactional注解依旧有效
@GetMapping("/test2")
public Object test2() {
String sql = "select * from student where id=:id";
HashMap map = new HashMap();
map.put("id", 1);
return jdbcTemplate.query(sql, map,new BeanPropertyRowMapper<>(StudentTable.class));
}
- NamedParameterJdbcTemplate查询返回结果可直接转为实体类
- 这种写法sql语句可以在代码中拼接做到复杂查询的效果
批量插入或者更新
String sql2 = "update student set name=:name";
List paraList = new ArrayList() {{
this.add("这是name");
}};
SqlParameterSource[] sqlParameterSources = new SqlParameterSource[paraList.size()];
for (int i = 0; i < paraList.size(); i++) {
HashMap paraMap = new HashMap<>();
paraMap.put("name", paraList.get(i));
sqlParameterSources[i] = new MapSqlParameterSource(paraMap);
}
jdbcTemplate.batchUpdate(sql2, sqlParameterSources);
结论
- 单表简单数据库操作用jpa自带的方法即可
- 复杂数据库操作还是直接写原生sql更加方便点