SpringBoot-数据交互

笔记参考于柏码知识库

JDBC交互框架

导入框架:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

JDBC模板类

配置MySQL数据源:

1
2
3
4
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
1
2
3
4
5
6
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

使用JdbcTemplate完成数据库查询:

1
2
3
4
5
6
7
8
@Resource
JdbcTemplate template;

@Test
void contextLoads() {
Map<String, Object> map = template.queryForMap("select * from user where id = ?", 1);
System.out.println(map);
}

编写自定义Mapper:

1
2
3
4
5
6
@Test
void contextLoads() {
User user = template.queryForObject("select * from user where id = ?",
(r, i) -> new User(r.getInt(1), r.getString(2), r.getString(3), r.getString(4)), 1);
System.out.println(user);
}

JDBC简单封装

使用SimpleJdbcInsert工具来进行插入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class WebConfiguration {

@Resource
DataSource source;

@Test
void contextLoads() {
//这个类需要自己创建对象
SimpleJdbcInsert simple = new SimpleJdbcInsert(source)
.withTableName("user") //设置要操作的表名称
.usingGeneratedKeyColumns("id"); //设置自增主键列
Map<String, Object> user = new HashMap<>(2); //插入操作需要传入一个Map作为数据
user.put("name", "bob");
user.put("email", "112233@qq.com");
user.put("password", "123456");
Number number = simple.executeAndReturnKey(user); //最后得到的Numver就是得到的自增主键
System.out.println(number);
}
}

JPA框架

最常用的JPA规范框架为Hibernate,SpringDataJPA也是采用Hibernate框架实现。

简单使用

导入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

使用注解在属性上添加数据库映射关系:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@Entity //表示这个类是一个实体类
@Table(name = "account") //对应的数据库中表名称
public class Account {

@GeneratedValue(strategy = GenerationType.IDENTITY) //生成策略,这里配置为自增
@Column(name = "id") //对应表中id这一列
@Id //此属性为主键
int id;

@Column(name = "username") //对应表中username这一列
String username;

@Column(name = "password") //对应表中password这一列
String password;
}

开启日志打印:

1
2
3
4
5
6
7
spring:
jpa:
#开启SQL语句执行日志信息
show-sql: true
hibernate:
#配置为检查数据库表结构,没有时会自动创建
ddl-auto: update

ddl-auto属性用于设置自动表定义,可以自动在数据库中创建表,表结构根据实体类决定,包括以下几种:

  • none: 不执行任何操作,数据库表结构需要手动创建。
  • create: 框架在每次运行时都会删除所有表,并重新创建。
  • create-drop: 框架在每次运行时都会删除所有表,然后再创建,但在程序结束时会再次删除所有表。
  • update: 框架会检查数据库表结构,如果与实体类定义不匹配,则会做相应的修改,以保持它们的一致性。
  • validate: 框架会检查数据库表结构与实体类定义是否匹配,如果不匹配,则会抛出异常。

为了访问表,需要创建Repository实现类:

1
2
3
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
}

插入操作:

1
2
3
4
5
6
7
8
9
10
@Resource
AccountRepository repository;

@Test
void contextLoads() {
Account account = new Account();
account.setUsername("小红");
account.setPassword("1234567");
System.out.println(repository.save(account).getId()); //使用save来快速插入数据,并且会返回插入的对象,如果存在自增ID,对象的自增id属性会自动被赋值,这就很方便了
}

查询:

1
2
3
4
5
@Test
void contextLoads() {
//默认通过通过ID查找的方法,并且返回的结果是Optional包装的对象,非常人性化
repository.findById(1).ifPresent(System.out::println);
}

方法名称拼接自定义SQL

可以通过方法名称拼接来进行条件查询等操作。

例如根据用户名模糊匹配查找:

1
2
3
4
5
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
//按照表中的规则进行名称拼接,不用刻意去记,IDEA会有提示
List<Account> findAllByUsernameLike(String str);
}

根据用户名和ID一起查询和判断是否存在某个ID的用户:

1
2
3
4
5
6
7
8
9
10
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {
List<Account> findAllByUsernameLike(String str);

Account findByIdAndUsername(int id, String username);
//也可以使用Optional类进行包装,Optional<Account> findByIdAndUsername(int id, String username);

//使用exists判断是否存在
boolean existsAccountById(int id);
}

关联查询

可以看做对象间的依赖关系。如用户详细信息作为用户信息的外键:

用户详细信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Data
@Entity
@Table(name = "users_detail")
public class AccountDetail {

@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
int id;

@Column(name = "address")
String address;

@Column(name = "email")
String email;

@Column(name = "phone")
String phone;

@Column(name = "real_name")
String realName;
}

用户信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Data
@Entity
@Table(name = "users")
public class Account {

@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
@Id
int id;

@Column(name = "username")
String username;

@Column(name = "password")
String password;

@JoinColumn(name = "detail_id") //指定存储外键的字段名称
@OneToOne //声明为一对一关系
AccountDetail detail;
}

在数据库表中,是使用详细信息表的主键作为外键:

1
2
3
Hibernate: alter table users add column detail_id integer
Hibernate: create table users_detail (id integer not null auto_increment, address varchar(255), email varchar(255), phone varchar(255), real_name varchar(255), primary key (id)) engine=InnoDB
Hibernate: alter table users add constraint FK7gb021edkxf3mdv5bs75ni6jd foreign key (detail_id) references users_detail (id)

查询结果中也会自动关联数据:

1
2
Hibernate: select account0_.id as id1_0_0_, account0_.detail_id as detail_i4_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_, accountdet1_.id as id1_1_1_, accountdet1_.address as address2_1_1_, accountdet1_.email as email3_1_1_, accountdet1_.phone as phone4_1_1_, accountdet1_.real_name as real_nam5_1_1_ from users account0_ left outer join users_detail accountdet1_ on account0_.detail_id=accountdet1_.id where account0_.id=?
Account(id=1, username=Test, password=123456, detail=AccountDetail(id=1, address=四川省成都市青羊区, email=8371289@qq.com, phone=1234567890, realName=本伟))

添加懒加载标记,可以在需要时才向数据库获取:

1
2
3
@JoinColumn(name = "detail_id")
@OneToOne(fetch = FetchType.LAZY) //将获取类型改为LAZY
AccountDetail detail;

修改数据也可以进行关联操作:

1
2
3
@JoinColumn(name = "detail_id")
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) //设置关联操作为ALL
AccountDetail detail;

关联操作设定包括4种,且可以并存:

  • ALL:所有操作都进行关联操作
  • PERSIST:插入操作时才进行关联操作
  • REMOVE:删除操作时才进行关联操作
  • MERGE:修改操作时才进行关联操作

对于多对多的关系,可以插入中间表来一一对应:

1
2
3
4
5
6
@ManyToMany(fetch = FetchType.LAZY)   //多对多场景
@JoinTable(name = "teach_relation", //多对多中间关联表
joinColumns = @JoinColumn(name = "cid"), //当前实体主键在关联表中的字段名称
inverseJoinColumns = @JoinColumn(name = "tid") //教师实体主键在关联表中的字段名称
)
List<Teacher> teacher;

JPQL自定义SQL语句

对于复杂查询,可以使用面向对象的JPQL语言:

1
2
3
4
5
6
7
8
@Repository
public interface AccountRepository extends JpaRepository<Account, Integer> {

@Transactional //DML操作需要事务环境,可以不在这里声明,但是调用时一定要处于事务环境下
@Modifying //表示这是一个DML操作
@Query("update Account set password = ?2 where id = ?1") //这里操作的是一个实体类对应的表,参数使用?代表,后面接第n个参数
int updatePasswordById(int id, String newPassword);
}

也可以使用原生SQL:

1
2
3
4
5
@Transactional
@Modifying
@Query(value = "update users set password = :pwd where username = :name", nativeQuery = true) //使用原生SQL,和Mybatis一样,这里使用 :名称 表示参数,当然也可以继续用上面那种方式。
int updatePasswordByUsername(@Param("name") String username, //我们可以使用@Param指定名称
@Param("pwd") String newPassword)

MybatisPlus

和JPA较为相似。支持链式调用:

1
2
3
4
5
6
7
8
@Test
void contextLoads() {
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper
.set("name", "lbw")
.eq("id", 1);
System.out.println(mapper.update(null, wrapper));
}

进一步将Service整合成模板,提供CRUD接口:

1
2
3
4
@Service
public interface UserService extends IService<User> {
//除了继承模版,我们也可以把它当成普通Service添加自己需要的方法
}
1
2
3
@Service   //需要继承ServiceImpl才能实现那些默认的CRUD方法
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

还可以根据数据库一键生成代码。