一、JDBC
JDBC是最基础的数据访问开发方式,也是应用最简单,执行效率最高的一种。SpringBoot和JDBCTemplate整合很简单,SpringBoot的自动配置已经配置好了JDBC,只需要自动注入JDBCTemplate,我们便可以直接使用。
@Autowired
private JdbcTemplate jt;
@RequestMapping("/selectName")
public String selectName(Integer id){
String sql = "select * from student where id = ?";
List<Map<String, Object>> list = jt.queryForList(sql, new Object[]{id});
return list.get(0).get("nickname").toString();
}
二、Spring Data Jpa
首先了解一下JPA,JPA是一个基于O/R映射的标准规范,同JDBC一样,只提供规则(注解、接口等),不提供实现,主要实现又Hibernate、OpenJPA等。Spring Data Jpa是在JPA规范下提供了Repository层的实现,但是使用哪一种ORM需要你来决定(默认使用Hibernate JPA的实现)。
Spring Data JPA建立数据访问十分简单,只需要继承JpaRepository接口即可。Spring Data JPA支持通过定义在Repository接口中的方法名来定义查询方法。
1、常规查询
List<Student> findByName(String name);
List<Student> findByNameLike(String name);
List<Student> findByNameAndGrade(String name, String grade);
方法名可以直接定义查询方法(例子中:1根据准确名,2根据名字模糊查询,3根据名字和年级查询),findBy也可以使用find、read、readBy、query、queryBy、get、getBy来替代,但是后面必须是实体类中属性字段。
Like和And这类关键字:
2、限制结果数量
List<Student> findTop10ByName(String name);//前10条
List<Student> findFirst30ByName(String name);//前30条
3、@NamedQuery查询
实体类中定义NamedQuery方法,在Repository使用已定义的查询语句:
@Entity
@NamedQuery(name = "Student.myFindBySex3", query = "select s from Student s where s.sex = ?1")
public class Student {
}
4、@Query查询
在Repository直接定义自己的查询语句,分为直接使用参数索引和命名参数两种方法:
//使用参数索引
@Query("select s from Student s where s.sex = ?1")
List<Student> myFindBySex1(String sex);
//使用参数命名
@Query("select s from Student s where s.sex = :sex")
5、排序和分页
List<Student> findBySex(String sex, Sort sort);
List<Student> findBySex(String sex, Pageable pageable);
6、更新
使用@Modifying和@Query组合来更新数据
@Modifying
@Transactional
@Query("update Student s set s.sex = ?1 where s.name = ?2")
int myUpdate(String sex, String name);
7、保存和删除
JpaRepository接口有保存和删除的方法,直接使用即可。
8、完整代码示例:
配置文件application.properties:
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/pingdian
spring.datasource.username=
spring.datasource.password=
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jackson.serialization.indent-output=true
spring.datasource是配置数据库的连接和连接池,spring.jpa.show-sql是用来设置hibernate操作时候在控制台打印真实的sql语句,spring.jackson.serialization.indent-output是让控制器输出的json字符串格式更美观,spring.jpa.hibernate.ddl-auto的配置有下面可选:
- creat:启动时删除表,根据实体类生成新表。
- create-drop:启动生成新表,sessionFactory关闭时删除表。
- update:启动时根据实体类生成表,当实体类属性变动时,表也会更新,数据不会删除。
- validate:启动时验证实体类和数据表有否一致。
- none:不采取任何操作。
/**
* 实体类
* @author think
*/
@Entity
@NamedQuery(name = "Student.myFindBySex3", query = "select s from Student s where s.sex = ?1")
public class Student {
@Id//映射主键
@GeneratedValue//默认自增
private Long id;
private String name;
private String sex;
.....其他字段和set、get方法
public Student(String name, String sex) {
this.name = name;
this.sex = sex;
}
public Student() {
super();
}
}
/**
* Spring Data JPA支持通过定义在Repository接口中的方法名来定义查询方法
* @author think
*/
public interface MyRepository extends JpaRepository<Student, Long> {
//使用参数索引
@Query("select s from Student s where s.sex = ?1")
List<Student> myFindBySex1(String sex);
//使用参数命名
@Query("select s from Student s where s.sex = :sex")
List<Student> myFindBySex2(@Param("sex") String sex);
//使用实体类中的@NamedQuery
List<Student> myFindBySex3(String sex);
//使用排序和分页
List<Student> findBySex(String sex, Sort sort);
List<Student> findBySex(String sex, Pageable pageable);
//更新
@Modifying
@Transactional
@Query("update Student s set s.sex = ?1 where s.name = ?2")
int myUpdate(String sex, String name);
}
/**
* 控制器
* @author think
*/
@RestController
public class StudentController {
@Resource
private MyRepository myRepository;
@RequestMapping("/selectBySex1")
public List<Student> selectBySex1(String sex){
return myRepository.myFindBySex1(sex);
}
@RequestMapping("/selectBySex2")
public List<Student> selectBySex2(String sex){
return myRepository.myFindBySex2(sex);
}
@RequestMapping("/selectBySex3")
public List<Student> selectBySex3(String sex){
return myRepository.myFindBySex3(sex);
}
@RequestMapping("/selectBySexSort")
public List<Student> selectBySexSort(String sex){
return myRepository.findBySex(sex, new Sort(Sort.Direction.DESC, "id"));
}
@RequestMapping("/selectBySexPage")
public List<Student> selectBySexPage(String sex){
return myRepository.findBySex(sex, new PageRequest(1,5));
}
@RequestMapping("/updateStudent")
public int updateStudent(String sex, String name){
return myRepository.myUpdate(sex, name);
}
@RequestMapping("/saveStudent")
public Student saveStudent(){
Student student = new Student("springboot", "女");
return myRepository.save(student);
}
@RequestMapping("/delStudent")
public void delStudent(){
myRepository.deleteById(1302L);
}
}
三、Mybatis
Mybatis不像Hibernate和JDBC都自动配置在SpringBoot中,Mybatis需要自己进行整合使用,下面是整合Mybatis和SpringBoot的方法:
1、使用mybatis generator 自动生成代码
创建新文件resources\generator\generatorConfig.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 数据库驱动:选择你的本地硬盘上面的数据库驱动包-->
<classPathEntry location="F:\mysql-connector-java-5.1.7-bin.jar"/>
<context id="DB2Tables" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressDate" value="true"/>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true"/>
</commentGenerator>
<!--数据库链接URL,用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/pingdian" userId="" password="">
</jdbcConnection>
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 生成模型的包名和位置-->
<javaModelGenerator targetPackage="com.model" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- 生成映射文件的包名和位置-->
<sqlMapGenerator targetPackage="mapping" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 生成DAO的包名和位置-->
<javaClientGenerator type="XMLMAPPER" targetPackage="com.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 要生成的表 tableName是数据库中的表名或视图名 domainObjectName是实体类名-->
<table tableName="Student" domainObjectName="Student" enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false"></table>
</context>
</generatorConfiguration>
选择Edit Configurations添加新的maven设置:
运行后自动生成使用表的实体类和mapper文件,在mapper类和xml文件中添加两个方法:
List<Student> selectAllStudent();
<select id="selectAllStudent" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from student
</select>
2、使用pageHelper插件
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- mybatis generator 自动生成代码插件 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile>
<overwrite>true</overwrite>
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
</build>
注意版本的使用,否则可能会报错,我用是SpringBoot2.0.2版本,所以pageHelper也用的最新的1.2.5版本。
3、添加配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/pingdian
spring.datasource.username=
spring.datasource.password=
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis.mapper-locations=classpath:mapping/*.xml
mybatis.type-aliases-package=com.model
pagehelper.helper-dialect=mysql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
pagehelper.params=count=countSql
pagehelper的配置可有可无,默认的配置可以满足使用情况。
4、编写controller层
@RestController
public class StudentController {
@Resource
private StudentMapper studentMapper;
@RequestMapping("/selectById")
public Student selectById(Integer id){
return studentMapper.selectByPrimaryKey(id);
}
@RequestMapping("/selectAll")
public List<Student> selectByAll(){
PageHelper.startPage(1,5);
return studentMapper.selectAllStudent();
}
}
在启动类中添加扫描注解(@MapperScan(“com”)),即可运行使用,注意扫描的包需要确定在mapper类的包上,否则会报错。
四、事务
Spring的事务机制在https://blog.csdn.net/zajiayouzai/article/details/80190524有说过,下面是SpringBoot中的事务使用:
1、自动配置的事务管理器
不管是使用JDBC还是JPA时,SpringBoot都自动配置了事务管理器,不同的访问技术有不同的事务管理器,但是他们都统一实现了PlatformTransactionManager接口:
/**
* JPA的事务管理器
*/
@EnableConfigurationProperties({JpaProperties.class})
@Import({Registrar.class})
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
private final JtaTransactionManager jtaTransactionManager;
@Bean
@ConditionalOnMissingBean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
.......
}
/**
* JDBC的事务管理器
*/
@Configuration
@ConditionalOnClass({JdbcTemplate.class, PlatformTransactionManager.class})
@AutoConfigureOrder(2147483647)
@EnableConfigurationProperties({DataSourceProperties.class})
public class DataSourceTransactionManagerAutoConfiguration {
public DataSourceTransactionManagerAutoConfiguration() {
}
@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {
private final DataSource dataSource;
private final TransactionManagerCustomizers transactionManagerCustomizers;
DataSourceTransactionManagerConfiguration(DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
this.dataSource = dataSource;
this.transactionManagerCustomizers = (TransactionManagerCustomizers)transactionManagerCustomizers.getIfAvailable();
}
@Bean
@ConditionalOnMissingBean({PlatformTransactionManager.class})
public DataSourceTransactionManager transactionManager(DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
}
}
2、示例:
@RequestMapping("/rollbackFor")
@Transactional(rollbackFor = {IllegalArgumentException.class})
public void rollbackFor(){
Student student = new Student("springboot", "女");
myRepository.save(student);
if (1 == 1){
throw new IllegalArgumentException("出现异常");
}
}
@RequestMapping("/noRollbackFor")
@Transactional(noRollbackFor = {IllegalArgumentException.class})
public void noRollbackFor(){
Student student = new Student("springboot", "女");
myRepository.save(student);
if (1 == 1){
throw new IllegalArgumentException("出现异常");
}
}
在StudentController添加两个方法后可以看见,异常抛出后,遇到异常回滚的rollbackFor没有插入数据,而遇到异常不会滚的noRollbackFor插入收据成功。
3、Mybatis的事务管理
JDBC和JPA数据访问方式,因为有自动配置的存在,所以不需要显示声明@EnableTransactionManagement,但是整合Mybatis时,需要声明@EnableTransactionManagement,开启声明式事务的支持。这样使用@Transactional开启事务便可以正常运行。
五、缓存
Spring定义了CacheManager接口用来同意不同的缓存技术,针对不同的缓存技术,需要实现不同的CacheManager:
SpringBoot为我们自动配置了缓存,默认使用SimpleCacheConfiguration,即使用ConcurrentMapCacheManager。使用spring.cache为前缀记性配置。
简单使用:
//@CachePut缓存更新或新增,缓存名为studentCache,key为student的id
@RequestMapping("/cachePut")
@CachePut(value = "studentCache", key="#student.id")
public Student cachePut(Student student){
return myRepository.save(student);
}
//@CacheEvict从studentCache中删除缓存中key为id的student,没有指定key,即传入参数就是key
@RequestMapping("/cacheEvict")
@CacheEvict(value = "studentCache")
public void cacheEvict(Long id){
//myRepository.deleteById(id);
}
//@Cacheable从studentCache把key为id的数据缓存到student中
@RequestMapping("/cacheable")
@Cacheable(value = "studentCache")
public Student cacheable(Long id){
return myRepository.myFindById(id);
}
注解含义:
- @Cacheable:方法执行前查看缓存是否有数据,如果有数据,则直接返回数据,如果没有调用方法,将方法的返回值放进索引。
- @CachePut:无论怎样,都将方法的返回值放到缓存中。
- @CacheEvict:将一条或者多条数据从缓存中删除。
- @Caching:组合多个注解在一个方法上。
Cacheable、CachePut、CacheEvict都有value属性,指定要缓存的名称;key属性指定数据再缓存中存储的键;可以理解为List集合的名称为value,List集合中存储Map,Map的key就是指定key;需要注意的是方法中需要指定key的值,否则会把传入的参数作为key,也就是说一个对象也可以是一个key。
1、Redis
1)application.properties配置:
spring.cache.type=redis
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.password=
spring.redis.port=6379
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-wait=0
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-active=100
spring.redis.lettuce.pool.max-wait=0
上面是切换缓存类型、jedis和lettuce客户端的配置,都是可以省略,SpringBoot为我们提供的默认配置满足上面的配置要求。
2)自动配置
SpringBoot已经我们自动配置了RedisCacheManager、LettuceConnectionFactory、JedisConnectionFactory、RedisTemplate、StringRedisTemplate等,默认配置基本可以满足我们使用要求。SpringBoot提供了RedisTemplate和StringRedisTemplate两个模板来进行数据操作(StringRedisTemplate只针对键值都是字符串类型的数据进行操作),通过opsForXX来操作不同的数据类型。需要指出的是对键值进行操作的时候需要对数据进行序列化,RedisTemplate默认使用的是JDKSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer。
需要注意的是,最新的Spring Data Redis默认的redis客户端为lettuce,而不是jedis了,并且RedisCacheManager的创建方式也已经更改,Spring Data Redis手册有很详细的使用方法:
/**
* redis手动配置
* @author think
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
//RedisTemplate主要设置序列化方式
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(om);
redisTemplate.setValueSerializer(serializer);//value的序列化使用Jackson2JsonRedisSerializer
redisTemplate.setKeySerializer(new StringRedisSerializer());//key的序列化使用StringRedisSerializer
return redisTemplate;
}
//RedisCacheManager主要设置事务行为和预定义缓存
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory){
return RedisCacheManager.create(redisConnectionFactory);
/*return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
.withInitialCacheConfigurations(Collections.singletonMap("predefined", RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()))
.transactionAware()
.build();*/
}
//使用Jedis客户端作为连接
/*@Bean
public JedisConnectionFactory jedisConnectionFactory(){
return new JedisConnectionFactory();
}*/
}
3)控制器
/**
* 控制器
* @author think
*/
@RestController
public class StudentController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/set")
public void set(){
ValueOperations valueOperations1 = redisTemplate.opsForValue();
valueOperations1.set("student", new Student("redis", "女"));//redisTemplate存储对象
ValueOperations valueOperations2 = stringRedisTemplate.opsForValue();//stringRedisTemplate存储字符串
valueOperations2.set("StringRedis", "这是StringRedis");
}
@RequestMapping("/getString")
public String getString(){
ValueOperations valueOperations = stringRedisTemplate.opsForValue();
String str = (String) valueOperations.get("StringRedis");
return str;
}
@RequestMapping("/getObject")
public Student getObject(){
ValueOperations valueOperations = redisTemplate.opsForValue();
Student student = (Student) valueOperations.get("student");
return student;
}
}
2、MongoDB
MongoDB提供以下几个注解的支持:
- @Document:映射领域对象与MongoDB的一个文档
- @Id:映射当前属性是ID
- @DbRef:当前属性将参考其他文档
- @Field:为文档的属性定义名称
- @Version:将当前属性作为版本
application.properties配置文件以“spring.data.mongodb”为前缀进行配置,我们直接使用默认配置。
/**
* 创建领域模型
* @author think
*/
public class Location {
private String place;
private String year;
public Location(String place, String year) {
this.place = place;
this.year = year;
}
......get和set方法
}
@Document//映射领域模型和MongoDB的文档
public class Student {
@Id//文档的id
private String id;
private String name;
private Integer age;
@Field("locs")//此属性在文档中的名称为locs
private Collection<Location> locations = new LinkedList<>();
public Student() {
super();
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
......get和set方法
}
/**
* 数据访问
* @author think
*/
public interface MyRepository extends MongoRepository<Student, Long> {
Student findByName(String name);//支持方法名查询
@Query("{'age':?0}")//支持Query查询,参数JSON字符串即可
List<Student> withQueryFindByAge(Integer age);
}
/**
* 控制器
* @author think
*/
@RestController
public class StudentController {
@Resource
private MyRepository myRepository;
@RequestMapping("/save")
public Student save(){
Student student = new Student("mongoDB", 20);
Collection<Location> locations = new LinkedList<>();
Location l1 = new Location("北京", "2018");
Location l2 = new Location("黑龙江", "2017");
Location l3 = new Location("山东", "2016");
locations.add(l1);
locations.add(l2);
locations.add(l3);
student.setLocations(locations);
return myRepository.save(student);
}
@RequestMapping("/ql1")
public Student ql1(String name){
return myRepository.findByName(name);
}
@RequestMapping("/ql2")
public List<Student> ql2(Integer age){
return myRepository.withQueryFindByAge(age);
}
}
直接访问即可,我们可以借助Robo 3T可视化界面来查看数据库的变动。