Java 课程设计:LWZ - Online Judge学生端(数据库设计部分)

数据库设计

由于 OJ 系统需要存储的数据很多,因此需要有较为全面的数据库设计。首先需要存储用户基本的用户名和密码信息,以及存储用户的类型是老师还是学生。接着为了支持同学的答题,数据库需要存储题目的信息,包括题干和答案等。值得一提的是,一道编程题会有多个测试点,因此编程题需要额外的一张表存储测试点。需要对同学的答案进行记录,需要有表存储不同题型用户的作答情况,同理也需要有一张表存储用户的成绩。在实际情况下教师一般不会一道题一道题地布置,而是一次性布置多道题,以题目集的形式来存储,所以需要表来存储题目集信息。同学加入班级之后,才需要完成自己班上的任务,老师也只能在自己的班级发布任务,因此需要有两张表分别存储班级信息和老师发布的任务信息。最后还需要一张表存储站内短信,数据库的总体设计如下。

用户部分

users 表

存储用户基本信息和用户类型。

题目部分

choice 表

存储选择题题目的数据。

judgment 表

存储判断题题目的数据。

编程题存储

program 表

存储编程题题目的数据。

testpoint 表

存储对应编程题的测试点。

subjective

存储主观题题目的数据。

collections 表

存储题目集里有哪些题目。

作答记录部分

codes 表

存储用户提交的代码和答题状态。

answer 表

存储用户主观题作答的答案。

score 表

存储用户对应题目集的得分情况。

班级管理部分

classes 表

存储老师创建的班级和同学加入的班级。

task 表

存储对应班级要完成的题目集。

站内短信部分

emails 表

存储用户发送的站内短信。

数据库读写

对数据库的读写采用 DAO 模式实现,DAO (DataAccessobjects) 数据存取对象是指位于业务逻辑和持久化数据之间,实现对持久化数据的访问的工作模式,具体可以参考博客:Java DAO 模式。在服务器实现了 3 个 DAO 模式接口,来支持裁判机的正常工作。

CodeRepositoryDAO 接口

CodeRepository 接口指定了向存储层进行代码数据交互的方法,代码和 UML 类图如下。

/**
* CodeRepositoryDAO 接口指定了向存储层进行代码数据交互的方法
* @author 乌漆 WhiteMoon
* @version 1.0
*/
public interface CodeRepositoryDAO {

	/**
	   * 该方法用于将向存储层提交某位同学的某道题的代码
	   * @param username 提交用户,String
	   * @param id 题目编号,Integer
	   * @param code 代码,String
	   * @param state 解题状态
	   * @return boolean:提交成功返回true,失败返回false
	   * @throws SQLException
	   */
	public boolean submitCode(String username, Integer id , String code, Integer state) throws SQLException;
	
	/**
	   * 该方法用于将在存储层查找题目是否提提交过
	   * @param username 提交用户,String
	   * @param id 题目编号,Integer
	   * @param state 解题状态
	   * @return boolean:已存在返回true,不存在返回false
	   * @throws SQLException
	   */
	public boolean selectCode(String username, Integer id) throws SQLException;
	
	/**
	   * 该方法用于将向存储层更新某位同学的某道题的代码
	   * @param username 提交用户,String
	   * @param id 题目编号,Integer
	   * @param code 代码,String
	   * @param state 解题状态
	   * @return boolean:提交成功返回true,失败返回false
	   * @throws SQLException
	   */
	public boolean updateCode(String username, Integer id , String code, Integer state) throws SQLException;

	/**
	   * 该方法用于返回数据库中对应题目的所有代码样本
	   * @param id 题目编号,Integer
	   * @return List<String>:存储所有代码的List集合
	   * @throws SQLException
	   */
	public List<String> selectAllCode(Integer id) throws SQLException;
}

PointRepositoryDAO 接口

PointRepositoryDAO 接口指定了从数据库获取测试点、题目题号和选择判断题答案数据的方法,代码和 UML 类图如下。

/**
* PointRepositoryDAO 接口指定了从数据库获取测试点数据的方法
* @author 林智凯
* @version 1.0
*/
public interface PointRepositoryDAO {

	/**
	   * 该方法用于将从存储层返回对应题目的测试点
	   * @param num 题目编号,Integer
	   * @return LinkedList<Testpoint>:List集合,存储对应题目的所有测试点:
	   * @throws SQLException
	   */
	public List<Testpoint> getTestpoint(Integer num) throws SQLException;
	
	/**
	   * 该方法用于将从存储层返回对应题目集包含的所有选择题题号
	   * @param num 题目集编号,Integer
	   * @return List<Integer>:List集合,存储对应题目集包含的所有选择题号
	   * @throws SQLException
	   */
	public List<Integer> getChoiceNum(Integer num) throws SQLException;
	
	/**
	   * 该方法用于将从存储层返回对应题目集包含的所有判断题题号
	   * @param num 题目集编号,Integer
	   * @return List<Integer>:List集合,存储对应题目集包含的所有判断题号
	   * @throws SQLException
	   */
	public List<Integer> getJudgmentNum(Integer num) throws SQLException;
	
	/**
	   * 该方法用于将从存储层返回对应题目集包含的所有编程题题号
	   * @param num 题目集编号,Integer
	   * @return List<Integer>:List集合,存储对应题目集包含的所有编程题号
	   * @throws SQLException
	   */
	public List<Integer> getProgrammtNum(Integer num) throws SQLException;
	
	/**
	   * 该方法用于将从存储层返回对应选择题的答案
	   * @param choiceNum List<Integer>题目集包含的所有选择题题号
	   * @return List<String>:List集合,存储对应题号对应的答案
	   * @throws SQLException
	   */
	public List<String> getChoiceAnswer(List<Integer> choiceNum) throws SQLException;
	
	/**
	   * 该方法用于将从存储层返回对应判断题的答案
	   * @param judgmentNum List<Integer>题目集包含的所有选择题题号
	   * @return List<Integer>:List集合,存储对应题号对应的答案
	   * @throws SQLException
	   */
	public List<String> getJudgmentAnswer(List<Integer> judgmentNum) throws SQLException;
}

ScoreRepositoryDAO 接口

ScoreRepositoryDAO 接口指定了访问修改存储层中同学作答情况和更新分数的方法,代码和 UML 类图如下。

/**
* ScoreRepositoryDAO 接口指定了修改存储层中同学分数的方法
* @author 林智凯
* @version 1.0
*/
public interface ScoreRepositoryDAO {
	
	/**
	   * 该方法根据用户的信息,更新题目集的选择题分数
	   * @param username String 用户名
	   * @param collectionId Integer 题目集id
	   * @param classId String 班级名
	   * @param grade Integer 
	   * @return boolean true为更新成功,false为更新失败
	   * @throws SQLException
	   */
	public boolean updateChoiceScore(String username, Integer collectionId, String classId, Integer grade) throws SQLException;
	
	/**
	   * 该方法根据用户的信息,更新题目集的判断题题分数
	   * @param username String 用户名
	   * @param collectionId Integer 题目集id
	   * @param classId String 班级名
	   * @param grade Integer 成绩
	   * @return boolean true为更新成功,false为更新失败
	   * @throws SQLException
	   */
	public boolean updateJudgmentScore(String username, Integer collectionId, String classId, Integer grade) throws SQLException;
	
	/**
	   * 该方法根据用户的信息,更新题目集的编程题题分数
	   * @param username String 用户名
	   * @param collectionId Integer 题目集id
	   * @param classId String 班级名
	   * @param grade Integer 成绩
	   * @return boolean true为更新成功,false为更新失败
	   * @throws SQLException
	   */
	public boolean updateProgrammingScore(String username, Integer collectionId, String classId, Integer grade) throws SQLException;
	
	/**
	   * 该方法实现搜索对应题目集的选择题总分
	   * @param collectionId Integer 题目集id
	   * @return Integer 题目集选择题总分
	   * @throws SQLException
	   */
	public Integer getChoiceScore(Integer collectionId) throws SQLException;
	
	/**
	   * 该方法实现搜索对应题目集的判断题总分
	   * @param collectionId Integer 题目集id
	   * @return Integer 题目集判断题总分
	   * @throws SQLException
	   */
	public Integer getJudgmentScore(Integer collectionId) throws SQLException;
	
	/**
	   * 该方法获取对应用户完成的编程题号
	   * @param questionId List<Integer> 题目id
	   * @param username String 用户名
	   * @return Integer 题目集判断题总分
	   * @throws SQLException
	   */
	public List<Integer> getProgramCompleteNum(List<Integer> questionId, String username) throws SQLException;
	
	/**
	   * 该方法获取用户答的题目的得分
	   * @param completeNum List<Integer> 已完成题目id
	   * @return Integer 用户答的题目的总分
	   * @throws SQLException
	   */
	public Integer getProgramScore(List<Integer> completeNum) throws SQLException;
}

自建数据库连接池

按照传统的模式,每次执行 SQL 语句访问数据库时,都需要先建立一条连接,等 SQL 语句执行完毕后切断连接。但是连接的建立需要一定的资源开销,如果需要频繁地对数据库执行 SQL 语句,则这种模式会在连接的建立上造成巨大的开销,导致效率受到影响。此处如果有一个数据库连接池能预先建立多条连接,当需要建立连接时就分出一条连接支持操作,然后再会收回连接池继续利用,就可以优化效率。
此处我根据数据库连接池的原理设计了简易的数据库连接池,使用的是类队列的结构实现的,并且模仿了 C++ vector 在连接数不足时进行 2 倍扩容。

package util;

import java.util.ArrayList;
import java.util.List;
import java.sql.*;

/**
* ConnectPool 类为自建的可扩容的简易数据库连接池
* @author 林智凯
* @version 1.0
*/
public class ConnectPool {
	
	private List<Connection> pool = new ArrayList<Connection>();
	private Integer maxSize;
	
	/**
	 * 数据库连接池的构造方法,预先分配5个Connection对象
	 */
	public  ConnectPool() throws SQLException {
		Integer num = 5;
		this.maxSize = num;
		for (int i = 0; i < num; i++) {
			Connection conn = MysqlConnect.connectDatabase();
			this.pool.add(conn);
		}
	}
	
	/**
	 * 向数据库连接池获取连接资源,返回一个 Connection 对象
	 * @return Connection 对象
	 */
	public Connection getConnection() throws SQLException {
		//检查是否还有Connection对象可以分配
		if(pool.size() == 0){
			//Connection对象不够用,先扩容
			for (int i = 0; i < this.maxSize; i++) {
				Connection conn = MysqlConnect.connectDatabase();
				pool.add(conn);
			}
			//2倍扩容
			this.maxSize *= 2;
		}
		//弹出第一个Connection对象返回
		Connection conn = pool.remove(0);
		return conn;
	}
	
	/**
	 * 回收 Connection 返回数据库连接池中。
	 * @param conn
	 */
	public void recoveryConnection(Connection conn){
		pool.add(conn);
	}

}

其中 MysqlConnect 类用于建立数据库连接的工具类,和 ConnectPool 类的关系用 UML 类图描述如下。

需要注意的是自建的 ConnectPool 类可能与 MySQL数据库的资源回收机制产生冲突,在特定的条件下连接会失效,因此建议使用连接池框架替换。

posted @ 2021-01-29 03:16  乌漆WhiteMoon  阅读(251)  评论(0编辑  收藏  举报