【Spring Boot】12.數據訪問技術基礎

簡介

前面的鋪墊工做,目前已經作得差很少了,接下來咱們將要學習有關Spring boot的數據訪問技術,包括:jdbc技術、MyBatis、Spring Data JPA,他着眼於整個JAVAEE。java

對於數據訪問層,不管是SQL仍是NOSQL,springboot默認採用整合Spring Data的方式進行統一處理,添加大量自動配置,屏蔽了不少設置。引入各類xxxTemplate、xxxResitory來簡化咱們對數據訪問層的操做。對咱們來講只須要進行簡單的設置便可。咱們將在接下來的學習過程當中學到SQL相關、NOSQL在緩存、消息、檢索等章節相關的內容。mysql

  • JDBC
  • MyBatis
  • JAP

Spring Data相關文檔:前往web

整合JDBC與數據源

經過springboot initializer建立一個選擇了web、jdbc、mysql幾個模塊。spring

依賴引入

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

能夠看到,引入了一個jdbc的場景啓動器,以及底層依賴mysql-connector-java,而且是運行時依賴runtimesql

在此以前,請注意上一節有關docker的內容,安裝好了mysql服務,個人地址是10.21.1.47:3306,其帳號密碼root:123456。若是您也在測試的話,請保證本身安裝了mysql服務,並正常啓動。docker

還要記得在mysql中建立一個本身的數據庫,我這裏建立了一個數據庫,名爲joyblack.數據庫

初步測試

咱們在配置文件中配置鏈接數據庫的配置:json

application.yml

server:
  port: 8086
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://10.21.1.47:3306/joyblack?characterEncoding=utf8&serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always

測試類JdbcwebApplicationTests

@RunWith(SpringRunner.class)
@SpringBootTest
public class JdbcwebApplicationTests {

    @Autowired
    DataSource dataSource;
    @Test
    public void contextLoads() throws SQLException {
        System.out.println(dataSource.getClass());
        System.out.println(dataSource.getConnection());
    }

}

com.mysql.jdbc.Driver 是 mysql-connector-java 5中的,com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6中的。數組

結果:緩存

  • 默認使用的是com.zaxxer.hikari.HikariDataSource做爲數據源,這是2.x版本哦,請留意。

因爲視頻這裏是基於1.x版本的,有興趣的同窗就去參考一下,其實配置原理和以前講的大同小異,不過2.0在某些地方則須要進一步研究,其引進了很多java8新特性。這裏就不在闡述了。

DataSourceInitializer:ApplicationListener

咱們觀察源碼:

/**
 * Initialize a {@link DataSource} based on a matching {@link DataSourceProperties}
 * config.
 */
class DataSourceInitializer {

	private static final Log logger = LogFactory.getLog(DataSourceInitializer.class);

	private final DataSource dataSource;

	private final DataSourceProperties properties;

	private final ResourceLoader resourceLoader;

	/**
	 * Create a new instance with the {@link DataSource} to initialize and its matching
	 * {@link DataSourceProperties configuration}.
	 * @param dataSource the datasource to initialize
	 * @param properties the matching configuration
	 * @param resourceLoader the resource loader to use (can be null)
	 */
	DataSourceInitializer(DataSource dataSource, DataSourceProperties properties,
			ResourceLoader resourceLoader) {
		this.dataSource = dataSource;
		this.properties = properties;
		this.resourceLoader = (resourceLoader != null) ? resourceLoader
				: new DefaultResourceLoader();
	}

	/**
	 * Create a new instance with the {@link DataSource} to initialize and its matching
	 * {@link DataSourceProperties configuration}.
	 * @param dataSource the datasource to initialize
	 * @param properties the matching configuration
	 */
	DataSourceInitializer(DataSource dataSource, DataSourceProperties properties) {
		this(dataSource, properties, null);
	}

	public DataSource getDataSource() {
		return this.dataSource;
	}

	/**
	 * Create the schema if necessary.
	 * @return {@code true} if the schema was created
	 * @see DataSourceProperties#getSchema()
	 */
	public boolean createSchema() {
		List<Resource> scripts = getScripts("spring.datasource.schema",
				this.properties.getSchema(), "schema");
		if (!scripts.isEmpty()) {
			if (!isEnabled()) {
				logger.debug("Initialization disabled (not running DDL scripts)");
				return false;
			}
			String username = this.properties.getSchemaUsername();
			String password = this.properties.getSchemaPassword();
			runScripts(scripts, username, password);
		}
		return !scripts.isEmpty();
	}

	/**
	 * Initialize the schema if necessary.
	 * @see DataSourceProperties#getData()
	 */
	public void initSchema() {
		List<Resource> scripts = getScripts("spring.datasource.data",
				this.properties.getData(), "data");
		if (!scripts.isEmpty()) {
			if (!isEnabled()) {
				logger.debug("Initialization disabled (not running data scripts)");
				return;
			}
			String username = this.properties.getDataUsername();
			String password = this.properties.getDataPassword();
			runScripts(scripts, username, password);
		}
	}

	private boolean isEnabled() {
		DataSourceInitializationMode mode = this.properties.getInitializationMode();
		if (mode == DataSourceInitializationMode.NEVER) {
			return false;
		}
		if (mode == DataSourceInitializationMode.EMBEDDED && !isEmbedded()) {
			return false;
		}
		return true;
	}

	private boolean isEmbedded() {
		try {
			return EmbeddedDatabaseConnection.isEmbedded(this.dataSource);
		}
		catch (Exception ex) {
			logger.debug("Could not determine if datasource is embedded", ex);
			return false;
		}
	}

	private List<Resource> getScripts(String propertyName, List<String> resources,
			String fallback) {
		if (resources != null) {
			return getResources(propertyName, resources, true);
		}
		String platform = this.properties.getPlatform();
		List<String> fallbackResources = new ArrayList<>();
		fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
		fallbackResources.add("classpath*:" + fallback + ".sql");
		return getResources(propertyName, fallbackResources, false);
	}

	private List<Resource> getResources(String propertyName, List<String> locations,
			boolean validate) {
		List<Resource> resources = new ArrayList<>();
		for (String location : locations) {
			for (Resource resource : doGetResources(location)) {
				if (resource.exists()) {
					resources.add(resource);
				}
				else if (validate) {
					throw new InvalidConfigurationPropertyValueException(propertyName,
							resource, "The specified resource does not exist.");
				}
			}
		}
		return resources;
	}

	private Resource[] doGetResources(String location) {
		try {
			SortedResourcesFactoryBean factory = new SortedResourcesFactoryBean(
					this.resourceLoader, Collections.singletonList(location));
			factory.afterPropertiesSet();
			return factory.getObject();
		}
		catch (Exception ex) {
			throw new IllegalStateException("Unable to load resources from " + location,
					ex);
		}
	}

	private void runScripts(List<Resource> resources, String username, String password) {
		if (resources.isEmpty()) {
			return;
		}
		ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
		populator.setContinueOnError(this.properties.isContinueOnError());
		populator.setSeparator(this.properties.getSeparator());
		if (this.properties.getSqlScriptEncoding() != null) {
			populator.setSqlScriptEncoding(this.properties.getSqlScriptEncoding().name());
		}
		for (Resource resource : resources) {
			populator.addScript(resource);
		}
		DataSource dataSource = this.dataSource;
		if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
			dataSource = DataSourceBuilder.create(this.properties.getClassLoader())
					.driverClassName(this.properties.determineDriverClassName())
					.url(this.properties.determineUrl()).username(username)
					.password(password).build();
		}
		DatabasePopulatorUtils.execute(populator, dataSource);
	}

}

​不難發現:

  • createSchema();運行建表語句;
  • initSchema(); 運行插入數據的sql語句;

同時,默認只須要將文件命名爲:

schema-*.sql、data-*.sql
默認規則:schema.sql,schema-all.sql;
可使用   
	schema:
      - classpath:department.sql
      指定位置

咱們建立一個建表文件

resources/schema.sql

/*
Navicat MySQL Data Transfer

Source Server         : docker
Source Server Version : 50505
Source Host           : 10.21.1.47:3306
Source Database       : joyblack

Target Server Type    : MYSQL
Target Server Version : 50505
File Encoding         : 65001

Date: 2018-12-19 14:03:49
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `user_name` varchar(20) NOT NULL,
  `login_name` varchar(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------

將其放在資源目錄下便可。運行項目,能夠看到生成了具體的數據庫表user;

接下來,咱們放置初始化數據表的文件

resources/data.sql

INSERT INTO `user` VALUES ('1', '阿庫婭', 'akuya');
INSERT INTO `user` VALUES ('2', '克里斯汀娜', 'cristiner');
INSERT INTO `user` VALUES ('3', '惠惠', 'huihui');

一樣將其放在資源目錄下便可。運行項目,能夠看到生成了具體的數據庫表user的3條用戶數據(爲美好的明天獻上祝福的女主角們);

sql-script-encoding: utf-8 在配置文件中配置此設置,保證中文不亂碼,若是還發現亂碼,請注意本身的鏈接url是否配置了characterEncoding=utf8

若報出The server time zone value '???ú±ê×??±??' is unrecognized...這樣的錯誤,請注意本身的鏈接url是否配置了serverTimeZone=GMT

2.x版本的springboot必須指定initialization-mode: always配置,每次重啓生成腳本纔會執行;

另外,咱們能夠從源碼中看到,該文件是能夠配置位置的

private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
        if (resources != null) {
            return this.getResources(propertyName, resources, true);
        } else {
            String platform = this.properties.getPlatform();
            List<String> fallbackResources = new ArrayList();
            fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
            fallbackResources.add("classpath*:" + fallback + ".sql");
            return this.getResources(propertyName, fallbackResources, false);
        }
    }

若是咱們配置了this.properties.getData(),則springboot會從該位置獲取初始化文件,而該值根據咱們前面講過的模式,能夠繼續追溯,就是

package org.springframework.boot.autoconfigure.jdbc

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    ...
    private List<String> schema;
    ...

也就是說,咱們只須要配置一個spring.datasource.schema的數組對象,指明這些初始化文件的位置就能夠了。

同理,另一個則是spring.datasource.data,配置這兩個參數就對應咱們上述的兩種文件了(初始化表結構;初始化表數據)。

咱們能夠隨便測試一下:

application.yml

server:
  port: 8086
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://10.21.1.47:3306/joyblack?characterEncoding=utf8&serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always
    sql-script-encoding: utf-8
    schema:
      - classpath:schema1.sql
    data:
      - classpath:data1.sql

在此以前,記得複製兩個文件爲xx1.sql。

這裏觀察源碼還發現兩個操做方法都調用了runScript,也就是說,其底層都是同樣的執行腳本方式,咱們大能夠將初始化表結構和添加數據的腳本混合成一個,可是可讀性差了一些,固然也更加的方便管理,利弊見仁見智了。

原生JDBC操做數據庫

這時候,咱們就能夠直接操做數據庫的數據了,這裏簡單的演示一下查詢操做,其餘的操做也差很少是這樣的模式。

咱們建立一個controller查詢數據

controller/HelloController.class

@RestController
public class IndexController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GetMapping("/query")
    public String index(){
        jdbcTemplate.execute("delete from user where id = 1");
        List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from user");
        
        return maps.toString();
    }
}

運行項目以後咱們會發現數據庫id爲1的user表數據被刪除,同事訪問網站首頁,能夠獲得以下的返回:

[{id=2, user_name=克里斯汀娜, login_name=cristiner}, {id=3, user_name=惠惠, login_name=huihui}]
相關文章
相關標籤/搜索