1. 程式人生 > >記慕課學習秒殺系統之DAO層(一)

記慕課學習秒殺系統之DAO層(一)

作為一名初學框架的菜鳥,記錄這一次在慕課學習整個框架基礎功能的過程,與大家共勉!

本專案利用SSM框架,完成了秒殺專案簡單的增刪改查功能,對初學框架的小菜鳥(比如我)有非常好的指導作用。 專案開發所用工具:IDEA開發環境,jdk1.8,Mysql 8.0.11,maven 3.5.3,以及mysql的視覺化工具Navicat

一、建立Maven專案,新增專案依賴

1.建立Maven webapp專案

原教程是通過命令列建立Maven專案骨架,我這裡直接利用IDEA新建Maven專案 IDEA建立Maven Web專案 注意Create from archetype要打勾,不然無法選擇; 另外要注意對照好骨架的名稱:maven-archetype-webapp

建成專案後的目錄結構如下所示 專案初步目錄結構 因為Maven預設建立專案的Servlet版本較低,因此需要修改web.xml的頭資訊來修改Servlet版本;查詢官方文件可以看到Servlet最新版本的web.xml配置頭資訊,複製替換進web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1"
         metadata-complete="true">
  <!-- 修改Servlet版本-->
</web-app>

緊接著,開啟專案結構,新增必要的資料夾 開啟專案結構 整體專案目錄結構如下所示 整體專案目錄結構 這裡介紹一下各資料夾的作用:

main:專案主體部分				test:單元測試
---java:存放java原始檔				---java:存放java單元測試原始檔
---resources:存放一些資源配置資訊		---resources:存放一些資源配置資訊
---sql:存放sql指令碼檔案

2.新增專案依賴

開啟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>org.seckill</groupId>
  <artifactId>seckill</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>seckill Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <!-- 使用junit4:支援註解方式-->
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!-- 補全專案依賴 -->
    <!-- 1:java日誌:slf4j,log4j,logback,common-logging
        slf4j是規範/介面
        日誌實現:log4j,logback,common-logging
        使用:slf4j+logback
     -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.12</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-core</artifactId>
      <version>1.1.1</version>
    </dependency>
    <!-- 實現slf4j介面並整合 -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.1.1</version>
    </dependency>
    <!--2:資料庫相關依賴 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.11</version>
      <!-- 指定作用域:專案執行時執行 -->
      <scope>runtime</scope> 
    </dependency>
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!-- DAO框架:MyBatis-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.2</version>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>
    <!-- Servlet web相關依賴 -->
    <!-- 標籤庫 -->
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.5.4</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
    </dependency>
    <!-- 4:Spring依賴-->
    <!-- 1)Spring核心依賴 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
    <!-- 2)Spring dao層依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
    <!-- 3)Spring web相關依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
    <!-- 4)Spring test相關依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.1.0.RELEASE</version>
    </dependency>
  </dependencies>
  
  <build>
    <finalName>seckill</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.20.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

二、DAO層設計與開發

1.資料庫設計與編碼

在sql資料夾中建立schema.sql檔案,編寫sql語句,完成資料庫的初始化工作

-- 資料庫初始化指令碼

-- 建立資料庫
CREATE  DATABASE seckill;
-- 使用資料庫
use seckill;
-- 建立秒殺庫存表
CREATE  TABLE  seckill(
`seckill_id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品庫存id',
`name` varchar(120) NOT NULL  COMMENT '商品名稱',
`number` int NOT NULL  COMMENT '庫存數量',
`start_time` timestamp NOT NULL  COMMENT '秒殺開啟時間',
`end_time` timestamp NOT NULL  COMMENT '秒殺結束時間',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
PRIMARY KEY (seckill_id),
key idx_start_time(start_time),
key idx_end_time(end_time),
key idx_create_time(create_time)
)ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='秒殺庫存表';

-- 初始化資料
insert into seckill(name,number,start_time,end_time)
values
  ('1000元秒殺iphone6',100,'2018-12-01 00:00:00','2018-12-02 00:00:00'),
  ('500元秒殺ipad2',200,'2018-12-01 00:00:00','2018-12-02 00:00:00'),
  ('300元秒殺小米4',300,'2018-12-01 00:00:00','2018-12-02 00:00:00'),
  ('200元秒殺紅米note',400,'2018-12-01 00:00:00','2018-12-02 00:00:00');

-- 秒殺成功明細表
-- 使用者登入認證相關的資訊
create table success_killed(
`seckill_id` bigint NOT NULL COMMENT '秒殺商品id',
`user_phone` bigint NOT NULL COMMENT '使用者手機號',
`state` tinyint NOT NULL DEFAULT -1 COMMENT '狀態標示:-1:無效 0:成功 1:已付款',
`create_time` timestamp NOT NULL COMMENT '建立時間',
PRIMARY KEY(seckill_id,user_phone),/*聯合主鍵*/
key idx_create_time(create_time)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='秒殺成功明細表';

在此我利用的是Navicat for MySQL視覺化工具,匯入上述sql檔案建立資料庫。

2.DAO實體和介面編碼

2.1 DAO實體編碼

在mian/java下建立兩個package: org.seckill.bean org.seckill.dao 分別存放實體bean和對應的dao介面 在這裡插入圖片描述 接著在bean中建立Seckill類(商品實體類),和SuccessKilled類(秒殺情況實體類),兩個類各屬性如下:

//商品實體類
public class Seckill {
    private long seckillId;		//商品ID
    private String name;		//商品名稱
    private int number;			//商品數量/庫存
    private Date startTime;		//秒殺開始時間
    private Date endTime;		//秒殺結束時間
    private Date createTime;		//該記錄建立時間
//秒殺狀態記錄類
public class SuccessKilled {
    private long seckillId;		//商品ID
    private long userPhone;		//使用者手機號
    private short state;		//狀態標示:-1:無效 0:成功 1:已付款
    private Date creatTime;		//該記錄建立時間
    private Seckill seckill;		//秒殺商品實體

另外需自動生成set/get方法,並重寫toString方法.

2.2 DAO介面編碼

在dao包下建立兩個介面(注意命名規範) SeckillDao SuccessKilledDao 分別對應bean中的兩個實體類

package org.seckill.dao;

import org.seckill.bean.Seckill;

import java.util.Date;
import java.util.List;

public interface SeckillDao {

    /**
     * 減庫存
     * @param seckillId
     * @param killTime
     * @return
     */
    int reduceNumber(@Param("seckillId") long seckillId, @Param("killTime") Date killTime);

    /**
     * 根據ID查詢秒殺物件
     * @param seckillId
     * @return
     */
    Seckill queryById(long seckillId);

    /**
     * 根據偏移量查詢秒殺商品列表
     * @param offset
     * @param limit
     * @return
     */
    List<Seckill> queryAll(@Param("offset") int offset, @Param("limit") int limit);
}
package org.seckill.dao;

import org.seckill.bean.SuccessKilled;

public interface SuccessKilledDao {

    /**
     * 插入購買明細,可過濾重複
     * @param seckillId
     * @param userPhone
     * @return
     */
    int insertSuccessKilled(long seckillId,long userPhone);

    /**
     * 根據id查詢SuccessKilled並攜帶秒殺產品物件例項
     * @param seckillId
     * @return
     */
    SuccessKilled queryByIdWithSeckill(@Param("seckillId") long seckillId, @Param("userPhone") long userPhone);
}

2.3 基於MyBatis實現DAO程式設計

在resources資料夾中建立一個資料夾mapper,用來存放之前在dao資料夾中編寫的dao類對映;另外再建立一個mybatis-config.xml檔案,用於編寫mybatis的各項配置資訊。在這裡插入圖片描述 查詢MyBatis的官方文件,找到最新版本xml配置檔案的頭資訊,將其複製在mybatis-config.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">

頭資訊+全域性屬性:

<?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>
    <!-- 配置全域性屬性 -->
    <settings>
        <!-- 使用JDBC的getGeneratedKeys獲取資料庫自增主鍵值 -->
        <setting name="useGeneratedKeys" value="true"/>
        <!-- 使用列別名替換列名 預設:true -->
        <setting name="useColumnLabel" value="true"/>
        <!-- 開啟駝峰命名轉換 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

然後在mapper資料夾中建立兩個對映配置檔案,注意與dao資料夾中的類名相同 SeckillDao.xml SuccessKilledDao.xml 同樣查詢MyBatis官方文件,可以查到mapper有關的xml檔案的頭資訊

首先編寫SeckillDao.xml檔案,主要就是為DAO介面方法提供sql語句,這就是Mybatis方便的地方之一:我們只用編寫dao層的介面和xml檔案的sql語句,具體的實現交給Mybatis自己處理。

<?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">
        
<!-- namespace為dao類的路徑 -->
<mapper namespace="org.seckill.dao.SeckillDao">
    <!-- 目的:為DAO介面方法提供sql語句配置 -->
    <!-- id即為dao類介面名 -->
    <update id="reduceNumber">
        <!-- 具體sql語句 -->
        update
          seckill
        set
          number =number -1
        where seckill_id=#{seckillId}
        and start_time<![CDATA[<=]]>#{killTime}
        and end_time>=#{killTime}
        and number >0
    </update>
    
    <select id="queryById" resultType="Seckill" parameterType="long">
        select  seckill_id ,name,number,start_time,end_time,create_time
        from seckill
        where seckill_id=#{seckillId}
    </select>

    <select id="queryAll" resultType="Seckill">
        select  seckill_id ,name,number,start_time,end_time,create_time
        from seckill
        order by create_time DESC
        limit #{offset},#{limit}
    </select>
</mapper>

然後編寫SuccessKilledDao.xml,這裡採用了別名替換,實現對多張表的聯合查詢。這裡也是Mybatis的優點之一:可以靈活的使用sql語句,更大的發揮sql技巧。

<?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="org.seckill.dao.SuccessKilledDao">
    <insert id="insertSuccessKilled">
        insert ignore into success_killed(seckill_id,user_phone,state)
        values (#{seckillId},#{userPhone},0)
    </insert>
    <select id="queryByIdWithSeckill" resultType="SuccessKilled">
        select
               sk.seckill_id,
               sk.user_phone,
               sk.create_time,
               sk.state,
               s.seckill_id "seckill.seckill_id",
               s.name "seckill.name",
               s.number "seckill.number",
               s.start_time "seckill.start_time",
               s.end_time "seckill.end_time",
               s.create_time "seckill.create_Time"
        from success_killed sk
        inner join seckill s on sk.seckill_id=s.seckill_id
        where sk.seckill_id=#{seckillId} and sk.user_phone=#{userPhone}
    </select>

</mapper>

3.MyBati與Spring的整合

通過mybatis與spring的整合,我們可以得到實現以下目標:

  • 更少的編碼:只寫介面,不寫實現
  • 更少的配置:自動掃描包;自動掃描xml配置檔案;自動注入Spring容器
  • 足夠的靈活性:自己定製SQL語句;自由傳參;結果集自動賦值;XML提供SQL,DAO介面提供Mapper

首先在resources資料夾下再建立一個資料夾spring,用來存放與spring相關的檔案 在spring資料夾中建立spring-dao.xml配置檔案,同樣查詢官方文件,找到對應版本的xml頭部,複製過來:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

這個檔案就存放spring的與dao相關的配置,同時還有與mybatis整合的配置,詳細程式碼如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 所有Spring使用資料庫都會用到的相關配置 -->
    <!-- 1:配置資料庫相關引數 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 2:資料庫連線池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>

        <!-- c3p0連線池的私有屬性 -->
        <!-- 資料庫最大連線物件 預設:15 -->
        <property name="maxPoolSize" value="30"/>
        <!-- 資料庫最小連線物件 預設:3 -->
        <property name="minPoolSize" value="10"/>
        <!-- 關閉連線後不自動commit 預設:false -->
        <property name="autoCommitOnClose" value="false"/>
        <!-- 獲取連線超時時間 預設:0,表示無限等待-->
        <property name="checkoutTimeout" value="1000"/>
        <!-- 當獲取連線失敗時重試次數 -->
        <property name="acquireRetryAttempts" value="2"/>
    </bean>

    <!-- 真正配置Mybatis整合Spring -->
    <!-- 3:配置SqlSessionFactory物件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入資料庫連線池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 配置Mybatis全域性配置檔案:mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!-- 掃描bean包 使用別名 -->
        <property name="typeAliasesPackage" value="org.seckill.bean"/>
        <!-- 掃描sql配置檔案:mapper需要的xml檔案 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!-- 4:配置掃描DAO介面包,動態實現DAO介面,注入到spring容器中
      不給id是因為該api在其他地方無引用,故不必給出id-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 給出需要掃描DAO介面包 -->
        <property name="basePackage" value="org.seckill.dao"/>
    </bean>
</beans>

到這個地方,Dao層的編寫算是完成了。接下來要寫一些單元測試,來測試我們的整個程式。

在test資料夾中建立一個java資料夾,然後可以在SeckillDao中選中介面名,按Ctrl+Shift+T組合鍵快速自動建立測試類 在這裡插入圖片描述 目錄結構及詳細程式碼如下: 在這裡插入圖片描述

package org.seckill.dao;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.seckill.bean.Seckill;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import javax.xml.crypto.Data;

import java.util.Date;
import java.util.List;


/**
 * 配置Spring和Junit整合,為了Junit啟動時載入SpringIOC容器
 * spring-test,junit
 */
@RunWith(SpringJUnit4ClassRunner.class)
//告訴junit spring配置檔案
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class SeckillDaoTest {

    //注入DAO實現類依賴
    @Resource
    private SeckillDao seckillDao;

    @Test
    public void queryById() {
        long id=1000;
        Seckill seckill=seckillDao.queryById(id);
        System.out.println(seckill.getName());
        System.out.println(seckill);
    }

    @Test
    public void queryAll() {
        List<Seckill> seckills=seckillDao.queryAll(0,100);
        for(Seckill seckill:seckills){
            System.out.println(seckill);
        }
    }

    @Test
    public void reduceNumber() {
        Date killTime = new Date();
        int updateCount = seckillDao.reduceNumber(1000L, killTime);
        System.out.println("updateCount=" + updateCount);
    }
}

同樣的,測試類SuccessKilledTest的詳細程式碼如下:

至此,秒殺系統DAO層編寫完畢。