Mybatis中的@SelectKey注解


一、創建Maven項目

在pom.xml中,添加mybatis依賴,mysql-jdbc依賴,把編譯版本改為1.8
你問,為啥mybatis不會自動依賴mysql-jdbc,需要手動寫明?答:因為mysql驅動是通過字符串動態加載的,這是一種“動態依賴”,Maven只能推導出“靜態依賴”。“動態依賴”是一種更加靈活的依賴。

Maven默認的Java版本是1.6,無法使用lambda表達式(1.8)和鑽石運算符(1.7)。

代碼片段:pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>wyf</groupId>
    <artifactId>xqweb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>
        
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>utf8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

創建好了pom.xml,就可以開始編碼了。最終的目錄結構如下,下面讓我們來一步一步創建文件。

二、配置Mybatis

代碼片段:mybatis.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="config.properties">
    </properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper class="haha.UserDao"/>
        <mapper resource="user.xml"/>
    </mappers>
</configuration>

代碼片段:config.properties

username=root
password=haha
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai

把配置信息跟mybatis.xml分開的好處是:更清晰。mybatis屬於代碼區,config.properties改起來比較簡單。

三、創建實體類User

User有三個屬性:name,age和id,重寫toString()方法便於調試。

package haha;
public class User {
String name;
Integer age;
Integer id;
public User(){}
public User(String name,int age){
   this.name=name;
   this.age=age;
}
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;
}

public Integer getId() {
   return id;
}

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

@Override
public String toString() {
   return String.format("(id:%d,name:%s,age:%d)", id, name, age);
}
}


相應的,在數據庫中建立一個表user

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=119 DEFAULT CHARSET=utf8mb4

四、實現UserDao接口

UserDao接口有兩個功能:插入、查詢全部。

package haha;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;

import java.util.List;

public interface UserDao {
@Insert("insert into user(name,age) value(#{name},#{age})")
int insert_withoutPrimaryKey(@Param("name") String name, @Param("age") int age);

int insert_useGeneratedKey(@Param("user") User user);

int insert_selectKey(@Param("user") User user);

@Insert("insert into user(name,age) value(#{user.name},#{user.age})")
@SelectKey(statement = "select last_insert_id()", keyProperty = "user.id", before = false, resultType = int.class)
int insert_selectKeyAnotation(@Param("user") User user);

@Select("select*from user")
List<User> getAll();
}

Mybatis寫SQL語句有兩種方式:1、使用注解;2、使用xml
對於比較長的SQL語句放在xml中,對於比較短的SQL語句放在注解中

在上面定義的UserDao中,insert_userGeneratedKey()和insert_selectKey()兩個函數沒有給出對應的SQL語句,需要在xml文件中進行定義。

代碼片段:user.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="haha.UserDao">
    <insert id="insert_useGeneratedKey" parameterType="haha.User"
            useGeneratedKeys="true" keyProperty="user.id">
        insert into user set id=#{user.id},name=#{user.name},age=#{user.age}
    </insert>
    <insert id="insert_selectKey" parameterType="haha.User">
        <selectKey keyProperty="user.id" keyColumn="id" order="AFTER" resultType="int">
            SELECT last_insert_id()
        </selectKey>
        insert into user(name,age) VALUE (#{user.name},#{user.age})
    </insert>
</mapper>

五、萬事俱備,只欠東風

編寫一個UserService類測試一下

package haha;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class UserService {

public static void main(String[] args) throws IOException {
   String resource = "mybatis.xml";
   InputStream inputStream = Resources.getResourceAsStream(resource);
   SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
   SqlSessionFactory factory = builder.build(inputStream);
   SqlSession session = factory.openSession();
   UserDao dao = session.getMapper(UserDao.class);
   //使用默認主鍵
   int affectedRows = dao.insert_withoutPrimaryKey("張三", 25);
   System.out.println(affectedRows);
   //使用useGeneratedKey,將主鍵注入到user.id中
   User u = new User("張三", 17);
   affectedRows = dao.insert_useGeneratedKey(u);
   System.out.println(affectedRows + " " + u.getId());
   //使用selectKey執行在插入之前或之后執行查詢語句
   affectedRows = dao.insert_selectKey(u);
   System.out.println(affectedRows + " " + u.getId());
   //使用selectKey注解的方式
   affectedRows = dao.insert_selectKeyAnotation(u);
   System.out.println(affectedRows + " " + u.getId());
   session.commit();
   List<User> a = dao.getAll();
   a.forEach(System.out::println);
}
}

六、insert()函數返回值

如下代碼,insert()函數的返回值為int類型,表示affectedRows,即受影響的行數,如果成功插入返回1,如果不成功插入,返回0。對於一切寫操作(insert,update,delete),返回值都是affectedRows。

@Insert("insert into user(name,age) value(#{name},#{age})")
int insert(@Param("name") String name, @Param("age") int age);

七、關於@SelectKey

關於insert()有一種需求很常見:如何確定插入數據的主鍵。對於MySQL中的自增類型主鍵,無需提供主鍵可以直接插入。還是以insert()函數為例,這個SQL語句沒有提供主鍵,主鍵是自增類型可以自動生成。

@Insert("insert into user(name,age) value(#{name},#{age})")
int insert(@Param("name") String name, @Param("age") int age);

下面介紹一個重要注解@SelctKey(statement="SQL語句",keyProperty="將SQL語句查詢結果存放到keyProperty中去",before="true表示先查詢再插入,false反之",resultType=int.class)
其中:

  • statement是要運行的SQL語句,它的返回值通過resultType來指定
  • before表示查詢語句statement運行的時機
  • keyProperty表示查詢結果賦值給代碼中的哪個對象,keyColumn表示將查詢結果賦值給數據庫表中哪一列
  • keyProperty和keyColumn都不是必需的,有沒有都可以
  • before=true,插入之前進行查詢,可以將查詢結果賦給keyProperty和keyColumn,賦給keyColumn相當於更改數據庫
  • befaore=false,先插入,再查詢,這時只能將結果賦給keyProperty
  • 賦值給keyProperty用來“讀”數據庫,賦值給keyColumn用來寫數據庫
  • selectKey的兩大作用:1、生成主鍵;2、獲取剛剛插入數據的主鍵。
  • 使用selectKey,並且使用MySQL的last_insert_id()函數時,before必為false,也就是說必須先插入然后執行last_insert_id()才能獲得剛剛插入數據的ID。

注意:

  • 該注解相當於XML配置中的<selectKey>的標簽
  • 與注解@Insert, @InsertProvider, @Update or @UpdateProvider搭配使用。在其他方法上將被忽略。
  • 如果你指定了一個@SelectKey注解,然后Mybatis將忽略任何生成的key屬性通過設置@Options,或者配置屬性。
  • 屬性: statement是要執行的sql語句的字符串數組, keyProperty是需要更新為新值的參數對象屬性, before可以是true或者false分別代表sql語句應該在執行insert之前或者之后, resultType是keyProperty的Java類型, statementType是語句的類型,取Statement, PreparedStatement和CallableStatement對應的STATEMENT, PREPARED或者CALLABLE其中一個,默認是PREPARED。

1、舉一個before=true的例子,新插入數據的id是當前表中行的個數

當before=true,可以通過SQL語句來填充insert語句中的某個參數,這個參數的名稱可以通過keyProperty來指明。

@Insert("insert into user value(#{id},#{name},#{age})")
@SelectKey(statement="select count(1)from user", keyProperty="id", before=true, resultType=int.class)
int insert(@Param("name") String name, @Param("age") int age);

這個函數返回值是affectedRows,也就是插入成功返回1,插入失敗返回0。
以上這段代碼有一個大大的隱患萬萬不能用在生產環境中。這個隱患就是:不能通過count()來確定id,多線程情況下有可能產生沖突。解決方案:可以使用UUID作為主鍵。

2、before=false的情況

注意keyProperty不能使基本類型,因為那樣賦值之后就找不到了(相當於傳值)
注解的方式

@Insert("insert into user(name,age) value(#{user.name},#{user.age})")
@SelectKey(statement = "select last_insert_id()", keyProperty = "user.id", before = false, resultType = int.class)
int insert_selectKeyAnotation(@Param("user") User user);

XML的方式

    <insert id="insert_selectKey" parameterType="haha.User">
        <selectKey keyProperty="user.id" keyColumn="id" order="AFTER" resultType="int">
            SELECT last_insert_id()
        </selectKey>
        insert into user(name,age) VALUE (#{user.name},#{user.age})
    </insert>

3、在Oracle中使用SelectKey生成主鍵,通常是“先查詢得到主鍵,再進行插入”

DUAL表是Oracle中的神奇的表
使用序列作為主鍵

<insert id="insertSelective" parameterType="com.zehin.vpaas.base.domain.SfyHazardAnalysis">  
<selectKey resultType="java.lang.Integer" order="BEFORE" keyProperty="hazardId">  
        SELECT SEQUENCE_1.NEXTVAL FROM DUAL  
</selectKey>  
insert into SFY_HAZARD_ANALYSIS  
<trim prefix="(" suffix=")" suffixOverrides=",">  
    HAZARD_ID,  
    <if test="hazardTime != null"> HAZARD_TIME,</if>  
    <if test="hazardTitle != null"> HAZARD_TITLE, </if>  
    <if test="hazardMeasure != null"> HAZARD_MEASURE, </if>  
    <if test="buildId != null"> BUILD_ID, </if>  
</trim>  
<trim prefix=" values(" suffix=")" suffixOverrides=",">  
    #{hazardId,jdbcType=INTEGER},  
    <if test="hazardTime != null">#{hazardTime,jdbcType=VARCHAR},</if>  
    <if test="hazardTitle != null"> #{hazardTitle,jdbcType=VARCHAR},  </if>  
    <if test="hazardMeasure != null"> #{hazardMeasure,jdbcType=VARCHAR},  </if>  
    <if test="buildId != null"> #{buildId,jdbcType= INTEGER}, </if>  
</trim>  
lt;/insert>  

使用GUID作為主鍵

<insert id="insertUser" parameterType="com.danny.mybatis.po.User"> 
<selectKey keyProperty="userId" order="BEFORE" resultType="java.lang.Integer"> 
select SYS_GUID() as userId from DUAL
 </selectKey> 
insert into T_USER(userId,userName,birthday,sex,address) values (#{userId},#{userName},#{birthday},#{sex},#{address}) </insert>

4、使用useGeneratedKeys

<insert id="insert" parameterType="Spares"     
        useGeneratedKeys="true" keyProperty="id">    
        insert into spares(spares_id,spares_name,    
            spares_type_id,spares_spec)    
        values(#{id},#{name},#{typeId},#{spec})    
    </insert>    
    <insert id="insertUser" useGeneratedKeys="true" keyColumn="id">
        insert into user(name,age) VALUE (#{name},#{age})
    </insert>

八、獲取剛剛插入數據的主鍵

除了使用selectKey的方式獲取剛剛插入數據的主鍵,還有以下方案:
1、如果是MySQL,可以用select last_insert_id()語句獲取新插入數據的主鍵。
2、如果主鍵類型是UUID,可以直接在代碼中生成主鍵進行插入,這樣就不需要從數據庫中讀取主鍵了,主動權掌握在代碼手中。

九、參考資料

Mybatis官方文檔
CSDN偶爾記一下:mybatis如何獲取oracle新插入數據記錄的主鍵?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM