开发颠覆者SpringBoot实战---------SpringBoot的数据访问学习

一、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;
    }
    ......getset方法
}
@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;
    }
    ......getset方法
}

/**
 * 数据访问
 * @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可视化界面来查看数据库的变动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值