前言
做Web应用需要内容,而内容通常存储于各种类型的数据库,服务端在接收到访问请求之后需要访问数据库获取并处理成展现给用户使用的数据形式。这篇博客打算总结一下spring boot中的数据访问模块,通过几个简单的实例完成helloworld级别的实例
JdbcTemplate数据访问
spring中的JdbcTemplate是自动配置的,我们在使用的时候只需要注入就可以了,可以说非常easy了。直接上实例吧。
这里为了简单,实例只贴出service和其实现类,后期会将相关代码上传到GitHub。
UserService
package com.learn.springBootDataAccess.service;
/**
*
* @author liman
* @createtime 2018年8月23日
* @contract 15528212893
* @comment:
* 用户服务接口
*/
public interface UserService {
/**
* 新增一个用户
* @param name
* @param age
*/
void create(String name, Integer age);
/**
* 根据name删除一个用户高
* @param name
*/
void deleteByName(String name);
/**
* 获取用户总量
*/
Integer getAllUsersSize();
/**
* 删除所有用户
*/
void deleteAllUsers();
}
userServiceImpl
package com.learn.springBootDataAccess.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**
*
* @author liman
* @createtime 2018年8月23日
* @contract 15528212893
* @comment:
* userService的是实现类
*/
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void create(String name, Integer age) {
jdbcTemplate.update(
"insert into user(name,age) values(?,?)",name,age);
}
@Override
public void deleteByName(String name) {
jdbcTemplate.update("delete from user where name=?",name);
}
@Override
public Integer getAllUsersSize() {
return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
}
@Override
public void deleteAllUsers() {
jdbcTemplate.update("delete from USER");
}
}
相关测试代码略去(非常easy,就不贴出来了)
总的来说,上述实例只是一个简介,只是引入了spring 中jdbcTemplate自身的一个使用方式。通常实际开发中很少用到JdbcTemplate。多会采用更为成熟的数据层访问框架。
JPA数据访问
JPA在我个人理解,其就算是一个数据访问层规范,Hibernate算是JPA的一种实现。
为了解决抽象各个Java实体基本的“增删改查”操作,我们通常会以泛型的方式封装一个模板Dao来进行抽象简化,但是这样依然不是很方便,我们需要针对每个实体编写一个继承自泛型模板Dao的接口,再编写该接口的实现。虽然一些基础的数据访问已经可以得到很好的复用,但是在代码结构上针对每个实体都会有一堆Dao的接口和实现。
由于模板Dao的实现,使得这些具体实体的Dao层已经变的非常“薄”,有一些具体实体的Dao实现可能完全就是对模板Dao的简单代理,并且往往这样的实现类可能会出现在很多实体上。Spring-data-jpa的出现正可以让这样一个已经很“薄”的数据访问层变成只是一层接口的编写方式。
还是以User的信息访问为例:
User实体类
package com.learn.springBootDataAccess.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.springframework.lang.Nullable;
/**
*
* @author liman
* @createtime 2018年8月23日
* @contract 15528212893
* @comment: UserInfo实体
*/
@Entity
public class UserInfo {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "UserInfo [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
很薄的dao层(原谅我依旧用老的概念来理解)
package com.learn.springBootDataAccess.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.learn.springBootDataAccess.domain.UserInfo;
public interface UserRepository extends JpaRepository<UserInfo, Long> {
/**
* 这个是自动实现的,jpa会按方法名去解析sql语句
* @param name
* @return
*/
UserInfo findByName(String name);
/**
*
* @param name
* @param age
* @return
*/
UserInfo findByNameAndAge(String name,Integer age);
@Query("from UserInfo u where u.name=:name")
UserInfo findUserSelf(@Param("name") String name);
/**
* 保存操作
*/
UserInfo save(UserInfo userInfo);
}
访问层中不需要编写任何的是实现代码,这就是Spring-data-jpa的一大特性:通过解析方法名创建查询,该接口继承自JpaRepository
,通过查看JpaRepository
接口的API文档,可以看到该接口本身已经实现了创建(save)、更新(save)、删除(delete)、查询(findAll、findOne)等基本操作的函数,因此对于这些基础操作的数据访问就不需要开发者再自己定义。
在我们实际开发中,JpaRepository
接口定义的接口往往还不够或者性能不够优化,我们需要进一步实现更复杂一些的查询或操作。由于本文重点在spring boot中整合spring-data-jpa,后面针对spring-data-jpa会进行深入的学习。
之后只需要在service层中输入UserRepository就可以了,下面直接给出测试代码
package com.learn.springBootDataAccess;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.learn.springBootDataAccess.domain.UserInfo;
import com.learn.springBootDataAccess.repository.UserRepository;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
public void findByNameTest() {
UserInfo user = userRepository.findByName("liman_18");
System.out.println(user);
}
@Test
public void findByNameAndAgeTest() {
UserInfo user = userRepository.findByNameAndAge("liman_19", 19);
System.out.println(user);
}
@Test
public void addUserInfoTest() {
UserInfo userInfo = new UserInfo();
userInfo.setAge(19);
userInfo.setName("liman_19");
userRepository.save(userInfo);
}
@Test
public void findUserSelf() {
String userName = "liman_19";
UserInfo userInfo = userRepository.findUserSelf(userName);
System.out.println(userInfo.toString());
}
}
相关配置:
需要引入jpa的maven依赖
<dependency
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
application.properties中加入相关配置用于数据访问
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.properties.hibernate.hbm2ddl.auto=update
spring.jpa.properties.hibernate.hbm2ddl.auto
是hibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:
create
:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。create-drop
:每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。update
:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。validate
:每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。
整合MyBatis
之前第一份实习工作做的一个完成的项目就是用的MyBatis,但是还是基于XML配置去完成,整个dao层非常的臃肿,各种数据访问文件加起来有几十个。这个实例虽然是helloworld,但是采用的是注解的方式实现相关功能。
数据访问层依旧之后接口,没有实现类。
对应的User实例:
这个时候的UserInfo实例不需要像hibernate一样打上各种注解
package com.learn.springbootMybatis.domain;
public class UserInfoM {
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "UserInfoM [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}
对应的数据访问层
package com.learn.springbootMybatis.repo.Mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.learn.springbootMybatis.domain.UserInfoM;
@Mapper
public interface UserMapper {
@Select("select * from user_info where name=#{name}")
UserInfoM findByName(@Param("name") String name);
@Insert("insert into user_info(name,age) values(#{name},#{age})")
int insert(@Param("name")String name,@Param("age") Integer age);
//使用Map数据结构进行数据插入
@Insert("insert into user_info(name,age) values(#{name,jdbcType=VARCHAR},#{age,jdbcType=INTEGER})")
int insertByMap(Map<String,Object> map);
@Insert("insert into user_info(name,age) values(#{name},#{age})")
int insertByUserInfoM(UserInfoM userInfo);
//修改和删除
@Update("update user_info set age=#{age} where name = #{name}")
void upateAge(UserInfoM userInfo);
@Delete("delete from user_info where id = #{id}")
void delete(Long id);
@Results({
@Result(property = "name",column="name"),
@Result(property = "age",column="age")
})
@Select("select name,age from user_info")
List<UserInfoM> findAll();
}
测试实现的代码
package com.learn.springbootMybatis;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.learn.springbootMybatis.domain.UserInfoM;
import com.learn.springbootMybatis.repo.Mapper.UserMapper;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void findByName() {
userMapper.insert("AAA", 20);
UserInfoM user = userMapper.findByName("AAA");
System.out.println(user);
}
@Test
public void insertByMap() {
Map<String,Object> map = new HashMap<String,Object>();
map.put("name", "liman");
map.put("age", 20);
userMapper.insertByMap(map);
}
@Test
public void insertByUserInfoM() {
UserInfoM userInfo = new UserInfoM();
userInfo.setAge(21);
userInfo.setName("huangrong");
userMapper.insertByUserInfoM(userInfo);
}
@Test
public void testUpdateUserInfoM() {
UserInfoM userInfo = userMapper.findByName("huangrong01");
System.out.println(userInfo);
userInfo.setAge(22);
userMapper.upateAge(userInfo);
}
@Test
public void testDelete() {
userMapper.delete(4L);
}
@Test
public void getAllListTest() {
List<UserInfoM> userList = userMapper.findAll();
for(UserInfoM userInfo:userList) {
System.out.println(userInfo);
}
}
}
MyBatis注解说明:
其实针对框架的使用,更多的是需要熟悉相关注解的含义。总体来说,就分为两个方面,一个是参数的传入,另一个就是返回参数的绑定。
参数的传入
1、使用@Param
@Insert("insert into user_info(name,age) values(#{name},#{age})")
int insert(@Param("name")String name,@Param("age") Integer age);
在函数参数中标记@Param标签,@Param中定义的name对应了SQL中的#{name},age同样
2、使用Map结构传入
@Insert("insert into user_info(name,age) values(#{name,jdbcType=VARCHAR},#{age,jdbcType=INTEGER})")
int insertByMap(Map<String,Object> map);
在insert的函数中,需要根据相关的属性,设置变量的值。 变量名称需要保证同名
@Test
public void insertByUserInfoM() {
UserInfoM userInfo = new UserInfoM();
userInfo.setAge(21);
userInfo.setName("huangrong");
userMapper.insertByUserInfoM(userInfo);
}
3、使用对象(最为常用)
@Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})")
int insertByUser(User user);
SQL语句中的name和age,对应User中的name和age属性。
@Test
public void testUpdateUserInfoM() {
UserInfoM userInfo = userMapper.findByName("huangrong01");
System.out.println(userInfo);
userInfo.setAge(22);
userMapper.upateAge(userInfo);
}
返回结果的绑定
只列出了单表的结果绑定(对于多表联合查询,暂时没有列出)
@Results({
@Result(property = "name",column="name"),
@Result(property = "age",column="age")
})
@Select("select name,age from user_info")
List<UserInfoM> findAll();
@Result中的property属性对应实体对象中的成员名,column对应SELECT出的字段名。