在線電影系統設計


在線電影系統設計介紹

項目地址:https://github.com/qitianfeng/yiying-parent ,后續會將項目的具體功能以文檔形式展示出來,各位可以點個star關注關注

簡介

在線電影系統是一個使用 B2C 的網站開發模式的在線視頻觀看及在線電影購票系統,主要分為前台用戶平台和后台運營管理平台。實現電影的上傳、解碼、存儲、點播

前后端系統的主要功能模塊

系統架構技術

項目的具體描述

在線電影系統分為前台門戶平台和后台管理平台,使用B2C模式,微服務技術架構,前后端分離開發。
前台的主要技術架構是:vue.js 、Nuxt.js 、Element-UI
后端的主要技術架構是:SpringBoot + SpringCloud + MyBatis-Plus + Dubbo + MySQL + Spring Cloud Getaway ;其他涉及到的中間件包括 Redis 、ElasticSearch 、令牌桶算法、FFMPEG 對視頻的解碼;業務中使用 EasyExcel 完成分類批量添加、JWT 用於前台門戶的分布式單點登錄;項目前后端分離開發,后端采用 Spring Cloud 微服務架構,持久層用的是 MyBatis-Plus,服務與服務之間使用 dubbo 進行 RPC 通信及使用 Swagger 技術生成各服務的接口文檔。前端系統則分為前台用戶系統和后台管理系統兩部分。

前台系統包括:首頁、電影中心、用戶中心。
其中首頁的主要分布為以下幾個部分

電影中心包括電影檢索頁面及電影的詳細信息頁面

其中電影的詳情頁面主要為用戶展示電影的基本信息:

電影的搜索頁面會將電影的分類信息進行展示,方便用戶對感興趣的分類信息進行檢索查看,並且查詢的關鍵字會進行高亮處理,給用戶帶來新的體驗效果。

電影的下單頁面分為兩個頁面

  1. 在線電影購票頁面,涉及座位的選座過程,以及動態計算選座過程的價格,實現真正的電影院選座和購買。

  1. 在線電影觀看購買頁面

用戶中心:注冊與登錄

  1. 后台管理系統包括:電影管理、電影分類管理、電影展廳管理

電影分類界面和電影展廳界面使用excel技術,將excel里的信息轉化進而存儲到數據庫中

電影展廳展示界面

電影管理主要用於添加新電影的基本信息,以及使用FFMPEG技術對上傳的視頻進行進一步的操作,進而發布完整電影信息

電影正式發布后的界面

項目初始化

使用WebIDE搭建項目

  1. 使用命令 mvn archetype:generate -DgroupId=com.yiying -DartifactId=yiying-parent -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

    (1)對命令相關參數的說明:

    • mvn:maven命令
    • archetype:generate:這是一個Maven插件,原型 archetype 插件是一個Maven項目模板工具包,可以用它創建基本的java項目結構。
    • -DgourpId: 組織名,公司網址的反寫 + 項目名稱
    • -DartifactId: 項目名(模塊名)
    • -Dversion:項目版本號
    • -DinteractiveMode:是否使用交互模式:false不使用,直接創建;true使用,需要根據提示輸入相關信息

    (2)修改 pom 文件

    • 添加 jar ,將項目打成jar包
  2. 導入 mybatis 相關依賴包,可以在https://mvnrepository.com網站中查詢 mybatis 的包版本號

      <dependencies>
    	<!-- Mybatis依賴包 3.5.3版本-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.3</version>
            </dependency>
    	<!-- Mysql版本依賴包-->        
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.47</version>
                <scope>runtime</scope>
            </dependency>
    	<!-- Lombok版本依賴包-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
            </dependency>
    	<!-- Junit測試依賴包-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies>
    
    

    再使用命令 mvn install 進行依賴打包

  3. 創建數據庫以及創建表

    --- 創建數據庫
    CREATE DATABASE test;
    --- 使用數據庫
    USE test;
    --- 創建相關表結構
    CREATE TABLE `user` (
       `id` char(19) NOT NULL COMMENT '會員id',
       `name` varchar(50) DEFAULT NULL COMMENT '昵稱',
       `age` tinyint(3) unsigned DEFAULT NULL COMMENT '年齡',
       PRIMARY KEY (`id`)
     ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='會員表';
    使用show tables 命令查看表是否創建成功
    
  4. 在resource文件下創建 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">
    <configuration>
        <!-- 設置駝峰匹配 -->
        <settings>
            <!--開啟二級緩存-->
            <setting name="cacheEnabled" value="true"/>
        </settings>
        <!-- 設置包掃描(別名) -->
        <typeAliases>
            <package name="com.yiying.entity"/>
        </typeAliases>
    <!-- 配置環境:可以配置多個環境,default:配置某一個環境的唯一標識,表示默認使用哪個環境 -->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">  <!--POOLED使用了連接池技術
    type=”POOLED”: MyBatis 會創建 PooledDataSource 實例
                    type = "UNPOOLED" mybatis會創建UnpooledDataSource實例
                    type = "JNDI"mybatis會從JNDI服務上查找DataSource實例,然后返回使用
                -->
                    <!-- 配置連接信息 -->
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                    <property name="username" value="root"/>
                    <property name="password" value="xxx"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- 配置映射文件:用來配置sql語句和結果集類型等 -->
        <mappers>
            <!--映射器2
            使用相對於類路徑的資源
            <mapper resource="mapper/UserMapper.xml"/>
            使用mapper接口的類路徑
            <mapper class=""/>
              掃描指定包下的mapper
            <package name=""></package>
            -->
            <mapper resource="mapper/UserMapper.xml"/>
        </mappers>
    </configuration>
    
    
  5. 創建 Mapper 對數據庫進行增刪改查操作

    • 創建數據庫表的映射類
    //數據庫中有多少字段對應類有多少屬性,不然會報錯
    @Data //lombok的注解,用此注解可以不用對屬性的getter和setter方法進行重寫
    public class User {
        private String id;
        private String name;
        private String age;
    }
    
    • 創建 UserMapper 類
    @Mapper
    public interface UserMapper {
    	//@Param 對傳入的數據進行綁定,當參數為一個時,可以選擇不加注解
        //根據id查詢用戶信息    
        public User getInfo(@Param("id") String id);
    	//查詢數據庫所有的用戶信息
        List<User> findAll();
        //增加一條用戶信息
        boolean insert(User user);
        //根據用戶id更新用戶信息
        boolean updateUser(User user);
        //根據用戶id刪除用戶信息
        boolean removeById(String id);
        //模糊查詢
        List<User> findByName(String username);
    }
    
  6. 在 resource/mapper 文件下創建 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類的路徑一致-->
    <mapper namespace="com.yiying.mapper.UserMapper">
    
        <!--根據id查詢用戶信息-->
         <select id="getInfo" resultType="com.yiying.entity.User" parameterType="java.lang.String">
            SELECT id, name,age
            FROM user
            WHERE id = #{id}
        </select>
        <resultMap id="userMap" type="user">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="age" property="age"/>
        </resultMap>
          <!--查詢所有用戶信息-->  
        <select id="findAll" resultMap="userMap" useCache="true">
            select * from user 
        </select>
        
          <!--模糊查詢-->
        <select id="findByName" resultType="com.yiying.entity.User">
            select *
            from user
            where name like '%${name}%'
        </select>
    
        <!--添加一條記錄-->
        <insert id="insert" parameterType="com.yiying.entity.User">
            insert into user(id,name,age)
            values(#{id},#{name},#{age})
        </insert>
        
        <!--根據用戶id修改用戶信息-->
        <update id="updateUser" parameterType="com.yiying.entity.User">
            update user set name=#{name},age=#{age} where id = #{id}
        </update>
        
        <!--根據用戶id刪除用戶-->
        <delete id="removeById" parameterType="java.lang.String">
            delete
            from user
            where id = #{id}
        </delete>
    </mapper>
    
  7. 對增刪改查操作進行測試

    // 首先需要在 test 文件夾中創建測試類
    //在處理測試時,先加載 init()  處理完測試后,加載 destory()
     //在執行方法前執行
        @Before
        public void init() throws Exception {
            //1.讀取配置文件
            in = Resources.getResourceAsStream("mybatis-config.xml");
            //2.創建構建者對象
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            //3.創建 SqlSession 工廠對象
            factory = builder.build(in);
            //4.創建 Dao 接口的實現類
            sqlSession = factory.openSession();
            //5.創建代理對象
            userMapper= sqlSession.getMapper(UserMapper.class);
        }
    	/**
         * 執行完方法后執行
         */
        @After
        public void destory(){
            sqlSession.commit();
            try {
                sqlSession.close();
                //釋放資源
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
   //查詢
   
   public class Test {
   
    private InputStream in;
       private SqlSessionFactory factory;
       SqlSession sqlSession;
       private UserMapper userMapper;

   //根據id查詢
       @Test
       public void test1(){
           User user = userMapper.getInfo("1");
        System.out.println(user);
       }
       
     @Test
       public void test2(){
        List<User> userList = userMapper.findAll();
           System.out.println("查詢的所有數據:" + userList.toString());
       }
   
    //模糊查詢
       @Test
       public void test3(){
             List<User>  user = userMapper.findByName("張三");
           System.out.println("根據名字模糊查詢結果為:"+user.toString());
       }
   }

//新增一條記錄
@Test
    public void test4(){
        User user = new User();
        user.setId("2");
        user.setName("張三四");
        user.setAge(21);
        boolean a = userMapper.insert(user);
        if(a){
            System.out.println("插入成功!!!!!!");
        } else {
            System.out.println("插入失敗!!!!!!");
        }
    }
    

//根據 id 更新用戶信息
	@Test
    public void test5(){
        User user = new User();
        user.setId("1");
        user.setName("張三四五");
        user.setAge(20);
        boolean a = userMapper.updateUser(user);
         if(a){
            System.out.println("修改成功!!!!!!");
        } else {
            System.out.println("修改失敗!!!!!!");
        }
    }

//根據id刪除用戶信息
@Test
 @Test
    public void test6(){
        boolean a = userMapper.removeById("2");
       if(a){
            System.out.println("插入成功!!!!!!");
        } else {
            System.out.println("插入失敗!!!!!!");
        }
    }

Mybatis的一級緩存和二級緩存

一級緩存

一級緩存介紹

在應用運行過程中,我們有可能在一次數據庫會話中,執行多次查詢條件完全相同的 SQL,MyBatis 提供了一級緩存的方案優化這部分場景,如果是相同的 SQL 語句,會優先命中一級緩存,避免直接對數據庫進行查詢,提高性能。具體執行過程如下圖所示。

img

每個 SqlSession 中持有了 Executor,每個 Executor 中有一個 LocalCache。當用戶發起查詢時,MyBatis 根據當前執行的語句生成 MappedStatement,在 Local Cache 進行查詢,如果緩存命中的話,直接返回結果給用戶,如果緩存沒有命中的話,查詢數據庫,結果寫入 Local Cache,最后返回結果給用戶。具體實現類的類關系圖如下圖所示。

img

一級緩存配置

我們來看看如何使用 MyBatis 一級緩存。開發者只需在MyBatis的配置文件中,添加如下語句,就可以使用一級緩存。共有兩個選項,SESSION或者 STATEMENT,默認是 SESSION 級別,即在一個 MyBatis 會話中執行的所有語句,都會共享這一個緩存。一種是 STATEMENT 級別,可以理解為緩存只對當前執行的這一個 Statement 有效。

<setting name="localCacheScope" value="SESSION"/>

一級緩存實驗

接下來通過實驗,了解 MyBatis 一級緩存的效果,每個單元測試后都請恢復被修改的數據。

首先是創建示例表 student,創建對應的 POJO 類和增改的方法,具體可以在 entity 包和 mapper 包中查看。

CREATE TABLE `student` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) COLLATE utf8_bin DEFAULT NULL,
  `age` tinyint(3) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

在以下實驗中,id為1的學生名稱是凱倫。

實驗1

開啟一級緩存,范圍為會話級別,調用三次 getStudentById,代碼如下所示:

public void getStudentById() throws Exception {
        SqlSession sqlSession = factory.openSession(true); // 自動提交事務
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        System.out.println(studentMapper.getStudentById(1));
        System.out.println(studentMapper.getStudentById(1));
        System.out.println(studentMapper.getStudentById(1));
    }

執行結果:

img

我們可以看到,只有第一次真正查詢了數據庫,后續的查詢使用了一級緩存。

實驗2

增加了對數據庫的修改操作,驗證在一次數據庫會話中,如果對數據庫發生了修改操作,一級緩存是否會失效。

@Test
public void addStudent() throws Exception {
        SqlSession sqlSession = factory.openSession(true); // 自動提交事務
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        System.out.println(studentMapper.getStudentById(1));
        System.out.println("增加了" + studentMapper.addStudent(buildStudent()) + "個學生");
        System.out.println(studentMapper.getStudentById(1));
        sqlSession.close();
}

執行結果:

img

我們可以看到,在修改操作后執行的相同查詢,查詢了數據庫,一級緩存失效

實驗3

開啟兩個 SqlSession,在 sqlSession1 中查詢數據,使一級緩存生效,在sqlSession2 中更新數據庫,驗證一級緩存只在數據庫會話內部共享。

@Test
public void testLocalCacheScope() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 

        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);

        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
        System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "個學生的數據");
        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}

img

sqlSession2 更新了id為1的學生的姓名,從凱倫改為了小岑,但 session1 之后的查詢中,id為1的學生的名字還是凱倫,出現了臟數據,也證明了之前的設想,一級緩存只在數據庫會話內部共享。

二級緩存

二級緩存介紹

在上文中提到的一級緩存中,其最大的共享范圍就是一個 SqlSession 內部,如果多個 SqlSession之間需要共享緩存,則需要使用到二級緩存。開啟二級緩存后,會使用 CachingExecutor 裝飾Executor,進入一級緩存的查詢流程前,先在 CachingExecutor 進行二級緩存的查詢,具體的工作流程如下所示。

img

二級緩存開啟后,同一個 namespace 下的所有操作語句,都影響着同一個 Cache,即二級緩存被多個 SqlSession 共享,是一個全局的變量。

當開啟緩存后,數據的查詢執行的流程就是 二級緩存 -> 一級緩存 -> 數據庫。

二級緩存配置

要正確的使用二級緩存,需完成如下配置的。

  1. 在MyBatis的配置文件中開啟二級緩存。
<setting name="cacheEnabled" value="true"/>
  1. 在 MyBatis 的映射 XML 中配置 cache 或者 cache-ref 。

cache標簽用於聲明這個 namespace 使用二級緩存,並且可以自定義配置。

<cache/>   
  • type:cache使用的類型,默認是 PerpetualCache,這在一級緩存中提到過。
  • eviction: 定義回收的策略,常見的有 FIFO,LRU。
  • flushInterval: 配置一定時間自動刷新緩存,單位是毫秒。
  • size: 最多緩存對象的個數。
  • readOnly: 是否只讀,若配置可讀寫,則需要對應的實體類能夠序列化。
  • blocking: 若緩存中找不到對應的key,是否會一直blocking,直到有對應的數據進入緩存。
  • cache-ref 代表引用別的命名空間的Cache配置,兩個命名空間的操作使用的是同一個 Cache。
<cache-ref namespace="mapper.StudentMapper"/>

二級緩存實驗

接下來我們通過實驗,了解 MyBatis 二級緩存在使用上的一些特點。

在本實驗中,id 為1的學生名稱初始化為點點。

實驗1

測試二級緩存效果,不提交事務,sqlSession1 查詢完數據后,sqlSession2 相同的查詢是否會從緩存中獲取數據。

@Test
public void testCacheWithoutCommitOrClose() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
        
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);

        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}

執行結果:

img

我們可以看到,當 sqlsession 沒有調用 commit() 方法時,二級緩存並沒有起到作用。

實驗2

測試二級緩存效果,當提交事務時,sqlSession1 查詢完數據后,sqlSession2 相同的查詢是否會從緩存中獲取數據。

@Test
public void testCacheWithCommitOrClose() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
        
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);

        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
        sqlSession1.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}

img

從圖上可知,sqlsession2 的查詢,使用了緩存,緩存的命中率是0.5。

實驗3

測試 update 操作是否會刷新該 namespace 下的二級緩存。

@Test
public void testCacheWithUpdate() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
        SqlSession sqlSession3 = factory.openSession(true); 
        
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
        
        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
        sqlSession1.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
        
        studentMapper3.updateStudentName("方方",1);
        sqlSession3.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}

img

我們可以看到,在 sqlSession3 更新數據庫,並提交事務后,sqlsession2 StudentMapper namespace 下的查詢走了數據庫,沒有走Cache。

實驗4

驗證MyBatis的二級緩存不適應用於映射文件中存在多表查詢的情況。

通常我們會為每個單表創建單獨的映射文件,由於MyBatis的二級緩存是基於 namespace 的,多表查詢語句所在的 namspace 無法感應到其他 namespace 中的語句對多表查詢中涉及的表進行的修改,引發臟數據問題。

@Test
public void testCacheWithDiffererntNamespace() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
        SqlSession sqlSession3 = factory.openSession(true); 
    
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
        
        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentByIdWithClassInfo(1));
        sqlSession1.close();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentByIdWithClassInfo(1));

        classMapper.updateClassName("特色一班",1);
        sqlSession3.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentByIdWithClassInfo(1));
}

執行結果:

img

在這個實驗中,我們引入了兩張新的表,一張 class,一張 classroom。class 中保存了班級的 id 和班級名,classroom中保存了班級 id 和學生 id。我們在 StudentMapper 中增加了一個查詢方法 getStudentByIdWithClassInfo,用於查詢學生所在的班級,涉及到多表查詢。在 ClassMapper 中添加了 updateClassName,根據班級 id 更新班級名的操作。

當 sqlsession1 的 studentmapper 查詢數據后,二級緩存生效。保存在 StudentMapper的 namespace 下的 cache 中。當 sqlSession3 的 classMapper`的 updateClassName 方法對class表進行更新時,updateClassName 不屬於StudentMapper 的 namespace,所以 StudentMapper 下的cache沒有感應到變化,沒有刷新緩存。當 StudentMapper 中同樣的查詢再次發起時,從緩存中讀取了臟數據。

實驗5

為了解決實驗4的問題呢,可以使用 Cache ref,讓 ClassMapper 引用 StudenMapper 命名空間,這樣兩個映射文件對應的 SQL 操作都使用的是同一塊緩存了。

執行結果:

img

不過這樣做的后果是,緩存的粒度變粗了,多個 Mapper namespace 下的所有操作都會對緩存使用造成影響。


免責聲明!

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



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