一、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可視化界面來查看數據庫的變動。