一、配置MySQL数据源和前置配置
1、引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入jdbc支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 连接MySQL数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
|
2、使用MySQL作为数据源
1
2
3
4
5
6
|
spring:
datasource:
username: root
password: root123
url: jdbc:mysql://127.0.0.1:3309/test?useUnicode=true&characterEncoding=utf8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
|
在使用新版本的MySQL的驱动时,使用的是这个驱动类com.mysql.cj.jdbc.Driver
,有些旧的连接MySQL5.x版本的驱动com.mysql.jdbc.Driver
。
SpringBoot默认是使用hikari
连接池,在各个连接池中性能是最高的,当然国内很流行地使用阿里的Druid
连接池。我们这里使用SpringBoot默认的hikari
连接池和默认的连接池配置即可。
3、编写配置类,检测数据库连接池类型
1
2
3
4
5
6
7
8
9
10
11
|
@Configuration
@Slf4j
public class DatasourceType implements ApplicationContextAware {
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
DataSource dataSource = context.getBean(DataSource.class);
log.info("--------------------------------------------");
log.info("using datasource {}", dataSource.getClass().getName());
log.info("--------------------------------------------");
}
}
|
上面的类中,实现了接口ApplicationContextAware
,其setApplicationContext方法是可以操作Spring的上下文,因此我们可以在上下文中拿到已经被注入的数据源,从而在日志中打印出来。其中@Slf4j
注解是引入的依赖lombok
中的功能,关于lombok
可以去了解一下,这里不讲述。
4、常见一个测试表student学生表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `t_student` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`age` int NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
|
5、创建实体类
1
2
3
4
5
6
7
8
|
@Data
public class Student {
private Long id;
private String name;
private String gender;
private Integer age;
}
|
6、编写SpringBoot启动类
1
2
3
4
5
6
7
|
@SpringBootApplication
public class JdbcApplication {
public static void main(String[] args) {
SpringApplication.run(JdbcApplication.class, args);
}
}
|
二、使用JdbcTemplate操作MySQL数据库
在刚开始学习Spring的时候,我们操作数据库都原生的JDBC来进行操作,那些步骤会很麻烦,查询之后的结果处理也是很繁琐,更别说事务处理了。Spring中封装了JDBC成JdbcTemplate,一些常用的操作都可以通过JdbcTemplate来进行操作。在Spring中也有其他的一些Template例如封装了Http客户端的RestTemplate。这也体现了SpringBoot的理念— 尽量减少程序员的配置,好了接下来直接使用JdbcTemplate了。
1、编写服务结果接口
1
2
3
4
5
6
7
8
9
10
11
12
|
public interface IStudentService {
Student findStudentById(Long id);
List<Student> findUsers(String name);
int insertStudent(Student student);
int updateStudent(Student student);
int deleteStudent(Long id);
}
|
2、IStudentService实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
@Service
public class StudentServiceImpl implements IStudentService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 拿到映射关系
*
* @return
*/
private RowMapper<Student> getStudentMapper() {
return (resultSet, i) -> {
Student student = new Student();
student.setId(resultSet.getLong("id"));
student.setName(resultSet.getString("name"));
student.setAge(resultSet.getInt("age"));
student.setGender(resultSet.getString("gender"));
return student;
};
}
@Override
public Student findStudentById(Long id) {
String sql = "select id,name,gender,age from t_student where id = ?";
Object[] param = new Object[]{id};
return jdbcTemplate.queryForObject(sql, param, getStudentMapper());
}
@Override
public List<Student> findUsers(String name) {
String sql = "select id,name,gender,age from t_student " +
"where name like concat('%',?,'%')";
Object[] param = new Object[]{name};
return jdbcTemplate.query(sql, param, getStudentMapper());
}
@Override
public int insertStudent(Student student) {
String sql = "insert into t_student(name,gender,age) value(?,?,?)";
return jdbcTemplate.update(sql, student.getName(), student.getGender(), student.getAge());
}
@Override
public int updateStudent(Student student) {
String sql = "update t_student set name=?,gender=?,age=? where id=?";
return jdbcTemplate.update(sql, student.getName(), student.getGender(), student.getAge(), student.getId());
}
@Override
public int deleteStudent(Long id) {
String sql = "delete from t_student where id = ?";
return jdbcTemplate.update(sql, id);
}
}
|
对于JdbcTemplate的映射关系是需要开发者自己区实现RowMapper接口,这样就可以完成数据表到实体类的关系映射。上述中对于RowMapper使用了Lambda表达式,最低需要Java8及以上的版本。
上述代码中实现了对于student表的CRUD即增删查改,写好sql之后,只需要传递参数即可,JdbcTemplate就会执行sql操作数据库,返回操作结果。
3、编写单元测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestApplication {
@Autowired
private IStudentService studentService;
@Test
public void test1() {
Student student = new Student();
student.setName("tom");
student.setGender("男");
student.setAge(18);
studentService.insertStudent(student);
student.setAge(20);
studentService.insertStudent(student);
student.setAge(22);
studentService.insertStudent(student);
}
@Test
public void test2() {
Student student = studentService.findStudentById(1L);
System.out.println(student.toString());
List<Student> all = studentService.findAll();
all.forEach(e -> System.out.println(e.toString()));
}
@Test
public void test3() {
Student student = new Student();
student.setId(1L);
student.setName("jack");
student.setGender("男");
student.setAge(18);
studentService.updateStudent(student);
Student db = studentService.findStudentById(1L);
System.out.println(db.toString());
}
@Test
public void test4() {
List<Student> tom = studentService.findUsers("tom");
tom.forEach(e -> System.out.println(e.toString()));
}
@Test
public void test5() {
studentService.deleteStudent(3L);
List<Student> all = studentService.findAll();
all.forEach(student -> System.out.println(student.toString()));
}
}
|
在上面的test3方法中,会执行两条sql,是由下面这两个方法发起的。在表面上看这两条sql实在同一个方法内执行,但是这两条sql是使用不同的数据库连接完成的。当JdbcTemplate执行下面的update
方法时,会从数据库连接池中获取一个连接执行sql,执行完毕之后会关闭数据库连接。当执行到queryForObject,又会从数据库连接池中获取一个连接执行sql。
很明显这样的操作是很浪费资源的,这样我们肯定是希望一个数据库连接内执行多条sql。我们可以使用StatementCallback
或者ConnectionCallback
接口实现回调。
1
2
|
jdbcTemplate.update(sql, student.getName(), student.getGender(), student.getAge(), student.getId());
jdbcTemplate.queryForObject(sql, param, getStudentMapper());
|
使用StatementCallback
实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public Student findStudentById2(Long id) {
return jdbcTemplate.execute(new StatementCallback<Student>() {
@Override
public Student doInStatement(Statement statement) throws SQLException, DataAccessException {
String sql1 = "select count(*) total from t_student where id = " + id;
ResultSet resultSet1 = statement.executeQuery(sql1);
while (resultSet1.next()) {
int total = resultSet1.getInt("total");
log.info("total:{}", total);
}
String sql2 = "select id,name,gender,age from t_student where id = " + id;
ResultSet resultSet2 = statement.executeQuery(sql2);
Student student = new Student();
while (resultSet2.next()) {
int row = resultSet2.getRow();
student = getStudentMapper().mapRow(resultSet2, row);
}
return student;
}
});
}
|
使用ConnectionCallback
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public Student findStudentById3(Long id) {
return jdbcTemplate.execute(new ConnectionCallback<Student>() {
@Override
public Student doInConnection(Connection connection) throws SQLException, DataAccessException {
String sql1 = "select count(*) total from t_student where id = ?";
PreparedStatement statement1 = connection.prepareStatement(sql1);
statement1.setLong(1, id);
ResultSet resultSet1 = statement1.executeQuery();
while (resultSet1.next()) {
int total = resultSet1.getInt("total");
log.info("total:{}", total);
}
String sql2 = "select id,name,gender,age from t_student where id = ?";
PreparedStatement statement2 = connection.prepareStatement(sql2);
statement2.setLong(1, id);
ResultSet resultSet2 = statement2.executeQuery();
Student student = new Student();
while (resultSet2.next()) {
int row = resultSet2.getRow();
student = getStudentMapper().mapRow(resultSet2, row);
}
return student;
}
});
}
|
三、使用JPA操作数据库
JPA (Java Persistence API) Java持久化API。是一套Java官方制定的ORM 方案。ORM(Object Relational Mapping)对象关系映射,在操作数据库之前,先把数据表与实体类关联起来。然后通过实体类的对象操作(增删改查)数据库表;所以说,ORM是一种实现使用对象操作数据库的设计思想。目前主流的ORM框架有Hibernate (JBoos)、EclipseTop(Eclipse社区)、OpenJPA (Apache基金会)、Hibernate是众多实现者之中,性能最好的。
Spring社区整合Hibernate在这上面做了增强,JPA就是依靠Hibernate实现的。使用JPA的开发人员操作实体类即可,不需要编写sql,它是通过一个持久化上下文(Persistence Context)来使用的。该上下文包含这三部分:
- 对象关系映射(ORM)描述,JPA支持注解或XML两种形式的描述,在Spring Boot中主要通过注解实现。
- 实体操作API,实现对实体对象的CRUD操作,来完成对象的持久化和查询。
- 查询语言,约定了面向对象的查询语言JPQL(Java Persistent Query Language),通过这层关系可以实现比较灵活的查询。
3.1 MySQL数据源和JPA的配置
1
2
3
4
5
6
7
8
9
10
11
12
13
|
spring:
datasource:
username: root
password: root123
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: update
naming:
# 驼峰命名映射
physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
|
spring.jpa.show-sql=true
在使用JPA执行sql时,把sql输出到日志显示。spring.jpa.hibernate.ddl-auto=update
该配置项是当实体类对应的表不存在时,会自动创建表,存在则不会。
none:禁止DDL处理
validate:验证架构,不对数据库做任何更改
update:表不存在时创建
create:每次启动都会删除表,然后重新创建
create-drop:每次会话结束之后,就删除表
spring.jpa.naming
就是关于实体类命名和数据库字段的映射处理。有physical-strategy
和implicit-strategy
。
- 第一步:如果我们没有使用@Table或@Column指定了表或字段的名称,则由SpringImplicitNamingStrategy为我们隐式处理,表名隐式处理为类名,列名隐式处理为字段名。如果指定了表名列名,SpringImplicitNamingStrategy不起作用。
- 第二步:将上面处理过的逻辑名称解析成物理名称。无论在实体中是否显示指定表名列名,SpringPhysicalNamingStrategy都会被调用。
所以如果我们想要自定义命名策略,可以根据自己的需求选择继承二者,并在配置文件中通过spring.jpa.hibernate.naming.implicit-strategy 或 spring.jpa.hibernate.naming.physical-strategy 进行指定自己的策略(例如为表名添加指定前缀)。
3.2 JPA开发实战
1、编写实体类
1
2
3
4
5
6
7
8
9
10
11
12
|
@Data
@Entity
@Table(name = "t_student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Convert(converter = SexConverter.class)
private SexEnum gender;
private Integer age;
}
|
@Data
在编译时生成getter、setter和toString方法。@Entity
声明该类是个实体类。@Table(name = "t_student")
声明该类关联的数据表。@Id
声明主键,@GeneratedValue(strategy = GenerationType.IDENTITY)
声明主键的生成策略是使用数据库自增Id。
性别枚举类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@Getter
public enum GenderEnum {
/**
* 1为男
*/
MALE(1,"男"),
/**
* 2为女
*/
FEMALE(2,"女");
private Integer id;
private String name;
GenderEnum(Integer id, String name) {
this.id = id;
this.name = name;
}
public static GenderEnum getSexById(int id){
for (GenderEnum genderEnum : GenderEnum.values()) {
if (genderEnum.getId()==id){
return genderEnum;
}
}
return null;
}
}
|
自定义的性别转换器
1
2
3
4
5
6
7
8
9
10
11
|
public class GengerConverter implements AttributeConverter<GenderEnum, Integer> {
@Override
public Integer convertToDatabaseColumn(GenderEnum genderEnum) {
return genderEnum.getId();
}
@Override
public GenderEnum convertToEntityAttribute(Integer id) {
return GenderEnum.getSexById(id);
}
}
|
2、定义JPA接口
Repository是Spring Data项目的顶层接口,它并没有定义方法,其子接口CrudRepository定了实体基本的增删查改的方法,功能性还不足够强大,PagingAndSortingRepository对CrudRepository的功能进行扩展并且提供了分页和排序的功能,最后JpaRepository同时集成了PagingAndSortingRepository和QueryByExampleExecutor,拥有了按照例子查询的功能。一般我们开发只需要扩展JpaRepository接口即可。
定义操作Student实体类的接口,只需要继承JpaRepository即可。这样我们就可以使用Spring Data Jpa帮我实现的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
/**
* 命名查询:通过name属性模糊查询
*
* @param name 学生名字
* @return 查询结果
*/
List<Student> findByNameLike(String name);
/**
* jql语言查询
* @param name
* @return
*/
@Query("from Student where name like concat('%',?1,'%')")
List<Student> getUsers(String name);
}
|
3、编写业务方法
IStudentService接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public interface IStudentService {
Student findStudentById(Long id);
List<Student> findUsers(String name);
Student insertStudent(Student student);
Student updateStudent(Student student);
void deleteStudent(Long id);
List<Student> findAll();
}
|
IStudentService实现类StudentServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
@Service
public class StudentServiceImpl implements IStudentService {
@Autowired
private StudentRepository studentRepository;
@Override
public Student findStudentById(Long id) {
return studentRepository.getOne(id);
}
@Override
public List<Student> findUsers(String name) {
return studentRepository.findByNameLike(name);
}
@Transactional(rollbackFor = RuntimeException.class)
@Override
public Student insertStudent(Student student) {
return studentRepository.save(student);
}
@Transactional(rollbackFor = RuntimeException.class)
@Override
public Student updateStudent(Student student) {
return studentRepository.save(student);
}
@Transactional(rollbackFor = RuntimeException.class)
@Override
public void deleteStudent(Long id) {
studentRepository.deleteById(id);
}
@Override
public List<Student> findAll() {
return studentRepository.findAll();
}
}
|
上面的代码中,通过Jap来操作数据十分简单,很多基本的CRUD都已经实现了,只需要传参进去即可。通过注解进行事务管理@Transactional(rollbackFor = RuntimeException.class)
,在执行多个sql时,只会在一个数据库连接内进行,避免了像使用JdbcTemplate的缺点。
3.3 使用JPA语言
1
2
3
4
5
6
7
|
/**
* jql语言查询
* @param name
* @return
*/
@Query("from Student where name like concat('%',?1,'%')")
List<Student> getUsers(String name);
|
使用JQL语言是要主要,主要操作的是实体类以及其属性,并不会去操作表。当然要使用原生的sql查询也可以,在@Query
里面添加一个属性nativeQuery=true即可。
3.4 使用命名查询
1
2
3
4
5
6
7
|
/**
* 命名查询:通过name属性模糊查询
*
* @param name 学生名字
* @return 查询结果
*/
List<Student> findByNameLike(String name);
|
命名查询的动词是find/get开始,by标识通过实体类的哪一个属性作为条件,Like是对这个属性进行模糊查询,类型还有升降序之类的。多个属性条件之间可以使用And或者Or逻辑处理。JPA会自动帮我们生成对应的sql语句。
四、总结
关于SpringBoot操作数据的入门就介绍到这里,关于更多的高级操作可能后续再做讲解。