開發顛覆者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可視化界面來查看數據庫的變動。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章