spring boot(四)資料訪問模組

謎一樣的Coder發表於2018-08-23

前言

做Web應用需要內容,而內容通常儲存於各種型別的資料庫,服務端在接收到訪問請求之後需要訪問資料庫獲取並處理成展現給使用者使用的資料形式。這篇部落格打算總結一下spring boot中的資料訪問模組,通過幾個簡單的例項完成helloworld級別的例項

JdbcTemplate資料訪問

spring中的JdbcTemplate是自動配置的,我們在使用的時候只需要注入就可以了,可以說非常easy了。直接上例項吧。

這裡為了簡單,例項只貼出service和其實現類,後期會將相關程式碼上傳到GitHub。

UserService

package com.learn.springBootDataAccess.service;

/**
 * 
 * @author liman
 * @createtime 2018年8月23日
 * @contract 15528212893
 * @comment:
 * 使用者服務介面
 */
public interface UserService {
    /**
     * 新增一個使用者
     * @param name
     * @param age
     */
    void create(String name, Integer age);

    /**
     * 根據name刪除一個使用者高
     * @param name
     */
    void deleteByName(String name);

    /**
     * 獲取使用者總量
     */
    Integer getAllUsersSize();

    /**
     * 刪除所有使用者
     */
    void deleteAllUsers();
}

userServiceImpl

package com.learn.springBootDataAccess.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**
 * 
 * @author liman
 * @createtime 2018年8月23日
 * @contract 15528212893
 * @comment:
 * userService的是實現類
 */
@Service("userService")
public class UserServiceImpl implements UserService {
	
	@Autowired
	private JdbcTemplate jdbcTemplate;

	@Override
	public void create(String name, Integer age) {
		jdbcTemplate.update(
				"insert into user(name,age) values(?,?)",name,age);
	}

	@Override
	public void deleteByName(String name) {
		jdbcTemplate.update("delete from user where name=?",name);
	}

	@Override
	public Integer getAllUsersSize() {
		return jdbcTemplate.queryForObject("select count(1) from USER", Integer.class);
	}

	@Override
	public void deleteAllUsers() {
		jdbcTemplate.update("delete from USER");
	}

}

相關測試程式碼略去(非常easy,就不貼出來了)

總的來說,上述例項只是一個簡介,只是引入了spring 中jdbcTemplate自身的一個使用方式。通常實際開發中很少用到JdbcTemplate。多會採用更為成熟的資料層訪問框架。

JPA資料訪問

JPA在我個人理解,其就算是一個資料訪問層規範,Hibernate算是JPA的一種實現。

為了解決抽象各個Java實體基本的“增刪改查”操作,我們通常會以泛型的方式封裝一個模板Dao來進行抽象簡化,但是這樣依然不是很方便,我們需要針對每個實體編寫一個繼承自泛型模板Dao的介面,再編寫該介面的實現。雖然一些基礎的資料訪問已經可以得到很好的複用,但是在程式碼結構上針對每個實體都會有一堆Dao的介面和實現。

由於模板Dao的實現,使得這些具體實體的Dao層已經變的非常“薄”,有一些具體實體的Dao實現可能完全就是對模板Dao的簡單代理,並且往往這樣的實現類可能會出現在很多實體上。Spring-data-jpa的出現正可以讓這樣一個已經很“薄”的資料訪問層變成只是一層介面的編寫方式。

還是以User的資訊訪問為例:

User實體類

package com.learn.springBootDataAccess.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.springframework.lang.Nullable;

/**
 * 
 * @author liman
 * @createtime 2018年8月23日
 * @contract 15528212893
 * @comment: UserInfo實體
 */

@Entity
public class UserInfo {

	@Id
	@GeneratedValue
	private Long id;

	@Column(nullable = false)
	private String name;

	@Column(nullable = false)
	private Integer age;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "UserInfo [id=" + id + ", name=" + name + ", age=" + age + "]";
	}

}

很薄的dao層(原諒我依舊用老的概念來理解)

package com.learn.springBootDataAccess.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.learn.springBootDataAccess.domain.UserInfo;

public interface UserRepository extends JpaRepository<UserInfo, Long> {

	/**
	 * 這個是自動實現的,jpa會按方法名去解析sql語句
	 * @param name
	 * @return
	 */
	UserInfo findByName(String name);
	
	/**
	 * 
	 * @param name
	 * @param age
	 * @return
	 */
	UserInfo findByNameAndAge(String name,Integer age);
	
	@Query("from UserInfo u where u.name=:name")
	UserInfo findUserSelf(@Param("name") String name);
	
	/**
	 * 儲存操作
	 */
	UserInfo save(UserInfo userInfo);
	
}

訪問層中不需要編寫任何的是實現程式碼,這就是Spring-data-jpa的一大特性:通過解析方法名建立查詢,該介面繼承自JpaRepository,通過檢視JpaRepository介面的API文件,可以看到該介面本身已經實現了建立(save)、更新(save)、刪除(delete)、查詢(findAll、findOne)等基本操作的函式,因此對於這些基礎操作的資料訪問就不需要開發者再自己定義。 

在我們實際開發中,JpaRepository介面定義的介面往往還不夠或者效能不夠優化,我們需要進一步實現更復雜一些的查詢或操作。由於本文重點在spring boot中整合spring-data-jpa,後面針對spring-data-jpa會進行深入的學習。

之後只需要在service層中輸入UserRepository就可以了,下面直接給出測試程式碼

package com.learn.springBootDataAccess;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.learn.springBootDataAccess.domain.UserInfo;
import com.learn.springBootDataAccess.repository.UserRepository;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTest {
	
	@Autowired
	private UserRepository userRepository;
	
	
	@Test
	public void findByNameTest() {
		UserInfo user = userRepository.findByName("liman_18");
		System.out.println(user);
	}
	
	@Test
	public void findByNameAndAgeTest() {
		UserInfo user = userRepository.findByNameAndAge("liman_19", 19);
		System.out.println(user);
	}
	
	@Test
	public void addUserInfoTest() {
		UserInfo userInfo = new UserInfo();
		userInfo.setAge(19);
		userInfo.setName("liman_19");
		userRepository.save(userInfo);
	}
	
	@Test
	public void findUserSelf() {
		String userName = "liman_19";
		UserInfo userInfo = userRepository.findUserSelf(userName);
		System.out.println(userInfo.toString());
	}
	
}

相關配置:

需要引入jpa的maven依賴

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

application.properties中加入相關配置用於資料訪問

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.jpa.properties.hibernate.hbm2ddl.auto=update

spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置屬性,其主要作用是:自動建立、更新、驗證資料庫表結構。該引數的幾種配置如下:

  • create:每次載入hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致資料庫表資料丟失的一個重要原因。
  • create-drop:每次載入hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除。
  • update:最常用的屬性,第一次載入hibernate時根據model類會自動建立起表的結構(前提是先建立好資料庫),以後載入hibernate時根據model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到伺服器後,表結構是不會被馬上建立起來的,是要等應用第一次執行起來後才會。
  • validate:每次載入hibernate時,驗證建立資料庫表結構,只會和資料庫中的表進行比較,不會建立新表,但是會插入新值。

 整合MyBatis

之前第一份實習工作做的一個完成的專案就是用的MyBatis,但是還是基於XML配置去完成,整個dao層非常的臃腫,各種資料訪問檔案加起來有幾十個。這個例項雖然是helloworld,但是採用的是註解的方式實現相關功能。

資料訪問層依舊之後介面,沒有實現類。

對應的User例項:

這個時候的UserInfo例項不需要像hibernate一樣打上各種註解

package com.learn.springbootMybatis.domain;

public class UserInfoM {

	private Long id;

	private String name;

	private Integer age;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "UserInfoM [id=" + id + ", name=" + name + ", age=" + age + "]";
	}

}

對應的資料訪問層

package com.learn.springbootMybatis.repo.Mapper;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import com.learn.springbootMybatis.domain.UserInfoM;

@Mapper
public interface UserMapper {
	
	@Select("select * from user_info where name=#{name}")
	UserInfoM findByName(@Param("name") String name);
	
	@Insert("insert into user_info(name,age) values(#{name},#{age})")
	int insert(@Param("name")String name,@Param("age") Integer age);
	
	//使用Map資料結構進行資料插入
	@Insert("insert into user_info(name,age) values(#{name,jdbcType=VARCHAR},#{age,jdbcType=INTEGER})")
	int insertByMap(Map<String,Object> map);
	
	@Insert("insert into user_info(name,age) values(#{name},#{age})")
	int insertByUserInfoM(UserInfoM userInfo);

	
	//修改和刪除
	@Update("update user_info set age=#{age} where name = #{name}")
	void upateAge(UserInfoM userInfo);
	
	@Delete("delete from user_info where id = #{id}")
	void delete(Long id);
	
	@Results({
		@Result(property = "name",column="name"),
		@Result(property = "age",column="age")
	})
	@Select("select name,age from user_info")
	List<UserInfoM> findAll();
}

測試實現的程式碼

package com.learn.springbootMybatis;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.learn.springbootMybatis.domain.UserInfoM;
import com.learn.springbootMybatis.repo.Mapper.UserMapper;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class UserMapperTest {

	@Autowired
	private UserMapper userMapper;
	
	@Test
	public void findByName() {
		userMapper.insert("AAA", 20);
		UserInfoM user = userMapper.findByName("AAA");
		System.out.println(user);
	}
	
	@Test
	public void insertByMap() {
		Map<String,Object> map = new HashMap<String,Object>();
		map.put("name", "liman");
		map.put("age", 20);
		userMapper.insertByMap(map);
	}
	
	@Test
	public void insertByUserInfoM() {
		UserInfoM userInfo = new UserInfoM();
		userInfo.setAge(21);
		userInfo.setName("huangrong");
		userMapper.insertByUserInfoM(userInfo);
	}
	
	@Test
	public void testUpdateUserInfoM() {
		UserInfoM userInfo = userMapper.findByName("huangrong01");
		System.out.println(userInfo);
		
		userInfo.setAge(22);
		userMapper.upateAge(userInfo);
	}
	
	@Test
	public void testDelete() {
		userMapper.delete(4L);
	}
	
	@Test
	public void getAllListTest() {
		List<UserInfoM> userList = userMapper.findAll();
		
		for(UserInfoM userInfo:userList) {
			System.out.println(userInfo);
		}
	}
}

MyBatis註解說明:

其實針對框架的使用,更多的是需要熟悉相關注解的含義。總體來說,就分為兩個方面,一個是引數的傳入,另一個就是返回引數的繫結。

引數的傳入

1、使用@Param

@Insert("insert into user_info(name,age) values(#{name},#{age})")
int insert(@Param("name")String name,@Param("age") Integer age);

在函式引數中標記@Param標籤,@Param中定義的name對應了SQL中的#{name},age同樣

2、使用Map結構傳入

@Insert("insert into user_info(name,age) values(#{name,jdbcType=VARCHAR},#{age,jdbcType=INTEGER})")
int insertByMap(Map<String,Object> map);

在insert的函式中,需要根據相關的屬性,設定變數的值。 變數名稱需要保證同名

	@Test
	public void insertByUserInfoM() {
		UserInfoM userInfo = new UserInfoM();
		userInfo.setAge(21);
		userInfo.setName("huangrong");
		userMapper.insertByUserInfoM(userInfo);
	}

3、使用物件(最為常用)

@Insert("INSERT INTO USER(NAME, AGE) VALUES(#{name}, #{age})")
int insertByUser(User user);

SQL語句中的name和age,對應User中的name和age屬性。

@Test
public void testUpdateUserInfoM() {
	UserInfoM userInfo = userMapper.findByName("huangrong01");
	System.out.println(userInfo);
		
	userInfo.setAge(22);
	userMapper.upateAge(userInfo);
}

返回結果的繫結

只列出了單表的結果繫結(對於多表聯合查詢,暫時沒有列出)

@Results({
	@Result(property = "name",column="name"),
	@Result(property = "age",column="age")
})
@Select("select name,age from user_info")
List<UserInfoM> findAll();

@Result中的property屬性對應實體物件中的成員名,column對應SELECT出的欄位名。

相關文章