引言
使用JavaSE相关知识完成一个以三层架构为设计规范的图书管理系统,不包括前端页面(使用main方法+Scanner()模拟用户输入),目的是为了基于一个项目快速了解三层架构的项目设计规范的实践。
开发流程
- 确认需求
- 导入相关的jar包和JDBCUtils工具类(工具类的目的是为了简化开发)
- 创建数据库表并准备基础数据
- 书写对应实体类
- 先分析功能执行所需的SQL支持,然后书写DAO层
- 书写DAO的Junit测试类
- 书写Service层+实现业务需求+事务控制
- 书写Service的Junit测试类
- 书写视图层,对接用户接收用户数据,并反馈结果
项目实战
一、确认需求
需求:想要创建一个图书管理系统,能够对书籍进行新增,修改,删除,查看所有书籍和查看单个书籍等功能,并且需要实现用户注册和登陆功能
项目结构图
导入jar包:
源代码结构图:
二、导入资源
导入相关jar包
在项目的结构下创建lib包,导入相关的jar包,并右键lib,选择添加为库(目的是为了可以直接使用import导包语句直接使用jar包中的类)
分析:
- 红色:spring封装JDBC的JDBCTemplate依赖jar包
- 黄色:mysql驱动jar包(8.0版本)
- 蓝色:Junit测试驱动jar包
jar包链接:
百度网盘:依赖jar包
阿里网盘:依赖jar包
导入JDBCUtils工具类
package com.csx.util;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static DataSource dataSource =null;
static{
try (
InputStream is=JDBCUtils.class.getResourceAsStream("/JDBCUtils.properties")
){
Properties p = new Properties();
p.load(is);
dataSource = new DriverManagerDataSource(p.getProperty("url"), p.getProperty("username"), p.getProperty("password"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static JdbcTemplate getJDBCTemplate(){
//创建JDBCTemplate对象并传入数据库连接池
JdbcTemplate template = new JdbcTemplate(dataSource);
return template;
}
/**
* 获取数据库连接池
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
/**
* 开始线程绑定 +获取连接
* @return
*/
public static Connection startTransaction(){
if (!TransactionSynchronizationManager.isSynchronizationActive()){
TransactionSynchronizationManager.initSynchronization();
}
Connection connection =DataSourceUtils.getConnection(dataSource);
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return connection;
}
/**
* 提交事务
* @param conn
*/
public static void commit(Connection conn){
try {
conn.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
clear(conn);
}
}
/**
* 回滚事务
* @param conn
*/
public static void rollback(Connection conn){
try {
conn.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
clear(conn);
}
}
/**
* 解除线程绑定+释放资源+归还连接到线程池
* @param conn
*/
public static void clear(Connection conn){
//清除线程绑定的资源
TransactionSynchronizationManager.clear();
TransactionSynchronizationManager.unbindResourceIfPossible(dataSource);
//归还数据库连接至连接池
if (conn!=null){//非空判断,判断为空再归还
DataSourceUtils.releaseConnection(conn,dataSource);
}
}
}
JDBCUtils.properties文件内容
配置文件放在src目录下,在src的直接子目录下(保存本地的数据库连接url,用户名和密码)
url=jdbc:mysql://localhost:3306/csx_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimeZone=Asia/shanghai
username=root
password=root
三、创建数据库表并准备基础数据
数据库名 csx_demo
创建两张表:t_user 和 t_book
创表语句不在赘述,保证user_id,book_id为主键即可,自行添加约束
四、书写实体类
可以参考我的另一篇博客:
设计之道:ORM、DAO、Service与三层架构的规范探索
User:
package com.csx.entity;
import java.io.Serializable;
public class User implements Serializable {
private Integer userId;
private String userName;
private String password;
public User(){}
public User(Integer userId, String userName, String password) {
this.userId = userId;
this.userName = userName;
this.password = password;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
Book:
package com.csx.entity;
import java.io.Serializable;
public class Book implements Serializable {
private Integer bookId;
private String bookName;
private String author;
private Double price;
private Integer stock;
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", bookName='" + bookName + '\'' +
", author='" + author + '\'' +
", price=" + price +
", stock=" + stock +
'}';
}
public Book(){}
public Book(Integer bookId, String bookName, String author, Double price, Integer stock) {
this.bookId = bookId;
this.bookName = bookName;
this.author = author;
this.price = price;
this.stock = stock;
}
public Integer getBookId() {
return bookId;
}
public void setBookId(Integer bookId) {
this.bookId = bookId;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
}
五、书写DAO层
分析业务实现需要的SQL支持,例如查询书籍信息需要select的SQL支持,新增数据需要insert的SQL支持等
UserDao:
package com.csx.dao;
import com.csx.entity.User;
public interface UserDao {
/**
* 查询根据用户名指定用户是否存在
* @param userName
* @return
*/
public User selectUserByUserName(String userName);
/**
* 新增用户信息
* @param user
* @return
*/
public int insertUser(User user);
}
UserDaoImpl:
package com.csx.dao.impl;
import com.csx.dao.UserDao;
import com.csx.entity.User;
import com.csx.util.JDBCUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class UserDaoImpl implements UserDao {
private JdbcTemplate template = JDBCUtils.getJDBCTemplate();
/**
* 查询根据用户名指定用户是否存在
*
* @param userName
* @return
*/
@Override
public User selectUserByUserName(String userName) {
String sql ="select * from t_user where user_name=?";
List<User> list = template.query(sql, new BeanPropertyRowMapper<>(User.class), userName);
return list.isEmpty()?null:list.get(0);
}
/**
* 新增用户信息
*
* @param user
* @return
*/
@Override
public int insertUser(User user) {
String sql="insert into t_user(user_name,password) values(?,?)";
int n = template.update(sql, user.getUserName(), user.getPassword());
return n;
}
}
BookDao:
package com.csx.dao;
import com.csx.entity.Book;
import java.util.List;
public interface BookDao {
/**
* 查询所有图书信息
* @return 返回查询到的所有图书信息
*/
public List<Book> selectBooks();
/**
* 根据book_name查询指定图书信息
* @param booName 图书名
* @return 指定图书信息
*/
public Book selectBookByBookName(String booName);
/**
* 插入图书信息
* @return
*/
public int insertBook(Book book);
/**
* 根据图书名删除图书
* @param bookName
* @return
*/
public int deleteBookByBookName(String bookName);
}
BookDaoImpl:
package com.csx.dao.impl;
import com.csx.dao.BookDao;
import com.csx.entity.Book;
import com.csx.util.JDBCUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import java.util.List;
public class BookDaoImpl implements BookDao {
private JdbcTemplate template = JDBCUtils.getJDBCTemplate();
/**
* 查询所有图书信息
*
* @return 返回查询到的所有图书信息
*/
@Override
public List<Book> selectBooks() {
String sql="select * from t_book";
List<Book> list = template.query(sql, new BeanPropertyRowMapper<>(Book.class));
return list;
}
/**
* 根据book_name查询指定图书信息
*
* @param booName 图书名
* @return 指定图书信息
*/
@Override
public Book selectBookByBookName(String booName) {
String sql="select * from t_book where book_name=?";
List<Book> list = template.query(sql, new BeanPropertyRowMapper<>(Book.class), booName);
return list.isEmpty()?null:list.get(0);
}
/**
* 插入图书信息
*
* @return
*/
@Override
public int insertBook(Book book) {
String sql="insert into t_book(book_name,author,price,stock) values(?,?,?,?)";
int n=template.update(sql,book.getBookName(),book.getAuthor(),book.getPrice(),book.getStock());
return n;
}
/**
* 根据图书名删除图书
*
* @param bookName
* @return
*/
@Override
public int deleteBookByBookName(String bookName) {
String sql="delete from t_book where book_name=?";
int n = template.update(sql, bookName);
return n;
}
}
六、书写DAO层的测试类
实际开发中,测试是很重要的一部分,理论上,我们应该对DAO和Service中所有的方法和功能都进行测试。
BookDaoImplTest:
package com.csx.test;
import com.csx.dao.BookDao;
import com.csx.dao.impl.BookDaoImpl;
import com.csx.entity.Book;
import org.junit.Test;
import java.util.List;
public class BookDaoImplTest {
private BookDao bookDao =new BookDaoImpl();
@Test
public void selectBooks(){
List<Book> books = bookDao.selectBooks();
books.forEach(book -> System.out.println(book));
}
@Test
public void selectBookByBookName(){
Book book = bookDao.selectBookByBookName("堂吉诃德");
System.out.println(book);
}
@Test
public void insertBook(){
Book book =new Book(null,"无敌烽火轮","csx",99.9,20);
int i = bookDao.insertBook(book);
if (i==1){
System.out.println("新增成功");
}else {
System.out.println("新增失败");
}
}
@Test
public void deleteBookByBookName(){
int i = bookDao.deleteBookByBookName("无敌烽火轮");
if (i==1){
System.out.println("删除成功");
}else {
System.out.println("删除失败");
}
}
}
UserDaoImplTest:
package com.csx.test;
import com.csx.dao.UserDao;
import com.csx.dao.impl.UserDaoImpl;
import com.csx.entity.User;
import org.junit.Test;
public class UserDaoImplTest {
private UserDao userDao =new UserDaoImpl();
@Test
public void selectUserByUserName(){
User user = userDao.selectUserByUserName("admin");
System.out.println(user);
}
@Test
public void insertUser(){
User user =new User(null,"test1","test");
int n=userDao.insertUser(user);
System.out.println(n);
}
}
七、书写Service层
Service层关注代码的功能实现,在这一层不仅要调用DAO的SQL支持完成功能逻辑,还需要关注事务执行的完整性(即事务的安全控制,保证多条SQL执行的完整性)
UserService:
package com.csx.service;
import com.csx.dao.UserDao;
import com.csx.dao.impl.UserDaoImpl;
public interface UserService {
/**
* 注册用户功能
* @param username 用户传递的用户名
* @param password 用户传递的密码
* @return 注册是否成功的boolean结果
*/
public boolean register(String username,String password);
/**
* 用户登录功能
* @param username 用户传递的用户名
* @param password 用户传递的密码
* @return 登录是否成功的boolean结果
*/
public boolean login(String username,String password);
}
UserServiceImpl:
package com.csx.service.impl;
import com.csx.dao.UserDao;
import com.csx.dao.impl.UserDaoImpl;
import com.csx.entity.User;
import com.csx.service.UserService;
import com.csx.util.JDBCUtils;
import java.sql.Connection;
public class UserServiceImpl implements UserService {
private UserDao userDao =new UserDaoImpl();
/**
* 注册用户功能
*
* @param username 用户传递的用户名
* @param password 用户传递的密码
* @return 注册是否成功的boolean结果
*/
@Override
public boolean register(String username, String password) {
boolean boo =false;
Connection conn =null;
try {
conn = JDBCUtils.startTransaction();
//业务功能
User u = userDao.selectUserByUserName(username);
if (u == null) {
User user = new User(null, username, password);
userDao.insertUser(user);
boo = true;
} else {
throw new RuntimeException("用户名重复,注册失败!");
}
JDBCUtils.commit(conn);
}catch (Exception e){
JDBCUtils.rollback(conn);
throw new RuntimeException(e);
}
return boo;
}
/**
* 用户登录功能
*
* @param username 用户传递的用户名
* @param password 用户传递的密码
* @return 登录是否成功的boolean结果
*/
@Override
public boolean login(String username, String password) {
boolean boo =false;
Connection conn =null;
try{
conn= JDBCUtils.startTransaction();
//业务功能
User user = userDao.selectUserByUserName(username);
//判断用户名是否存在
if (user!=null){
//判断密码是否正确
if (user.getPassword().equals(password)){
boo=true;
}else {
throw new RuntimeException("密码错误!");
}
}else {
throw new RuntimeException("用户不存在!");
}
JDBCUtils.commit(conn);
}catch (Exception e){
JDBCUtils.rollback(conn);
throw new RuntimeException(e);
}
return boo;
}
}
BookService:
package com.csx.service;
import com.csx.entity.Book;
import java.util.List;
public interface BookService {
/**
* 查询所有图书信息
* @return
*/
public List<Book> queryBooks();
/**
* 根据图书名查询指定图书信息
* @param bookName
* @return
*/
public Book queryBookByBookName(String bookName);
/**
* 插入新的图书信息,图书名不可重复
* @param book
* @return
*/
public boolean addBook(Book book);
/**
* 根据图书名删除指定图书信息
* @param bookName
* @return
*/
public boolean removeBookByBookName(String bookName);
}
BookServiceImpl:
package com.csx.service.impl;
import com.csx.dao.BookDao;
import com.csx.dao.impl.BookDaoImpl;
import com.csx.entity.Book;
import com.csx.service.BookService;
import com.csx.util.JDBCUtils;
import java.sql.Connection;
import java.util.List;
public class BookServiceImpl implements BookService {
private BookDao bookDao =new BookDaoImpl();
/**
* 查询所有图书信息
*
* @return
*/
@Override
public List<Book> queryBooks() {
return bookDao.selectBooks();
}
/**
* 根据图书名查询指定图书信息
*
* @param bookName
* @return
*/
@Override
public Book queryBookByBookName(String bookName) {
return bookDao.selectBookByBookName(bookName);
}
/**
* 插入新的图书信息,图书名不可重复
*
* @param book
* @return
*/
@Override
public boolean addBook(Book book) {
boolean boo =false;
Connection conn =null;
try{
conn= JDBCUtils.startTransaction();
//业务功能
Book b = bookDao.selectBookByBookName(book.getBookName());
if (b==null){
bookDao.insertBook(book);
boo=true;
}else {
throw new RuntimeException("图书存在,无法重复添加!");
}
JDBCUtils.commit(conn);
}catch (Exception e){
JDBCUtils.rollback(conn);
throw new RuntimeException(e);
}
return boo;
}
/**
* 根据图书名删除指定图书信息
*
* @param bookName
* @return
*/
@Override
public boolean removeBookByBookName(String bookName) {
return bookDao.deleteBookByBookName(bookName)==1?true:false;
}
}
这里主要展示完整的实现流程,至于如何实现事务控制,我会在其他博客中详细说明
八、书写Service层的测试类
BookServiceImplTest:
package com.csx.test;
import com.csx.entity.Book;
import com.csx.service.BookService;
import com.csx.service.impl.BookServiceImpl;
import org.junit.Test;
import java.util.List;
public class BookServiceImplTest {
private BookService bookService =new BookServiceImpl();
@Test
public void queryBooks(){
List<Book> books = bookService.queryBooks();
books.forEach(book -> System.out.println(book));
}
@Test
public void queryBookByBookName(){
Book book = bookService.queryBookByBookName("伪君子");
System.out.println(book);
}
@Test
public void addBook(){
boolean n= bookService.addBook(new Book(null,"海鲜养殖","海王",22.4,100));
if (n){
System.out.println("新增成功");
}else {
System.out.println("新增失败");
}
}
@Test
public void removeBookByBookName(){
boolean b = bookService.removeBookByBookName("海鲜养殖");
if (b){
System.out.println("删除成功");
}else {
System.out.println("删除失败");
}
}
}
UserServiceImplTest:
package com.csx.test;
import com.csx.service.UserService;
import com.csx.service.impl.UserServiceImpl;
import org.junit.Test;
public class UserServiceImplTest {
private UserService userService =new UserServiceImpl();
@Test
public void register(){
boolean b = userService.register("root1", "root");
if (b){
System.out.println("注册成功");
}else {
System.out.println("注册失败");
}
}
@Test
public void login(){
boolean login = userService.login("root", "123456");
if (login){
System.out.println("登录成功");
}else {
System.out.println("登录是吧");
}
}
}
九、书写视图层
package com.csx.view;
import com.csx.entity.Book;
import com.csx.service.BookService;
import com.csx.service.UserService;
import com.csx.service.impl.BookServiceImpl;
import com.csx.service.impl.UserServiceImpl;
import java.util.List;
import java.util.Scanner;
public class BookManagerView {
public static void main(String[] args) {
//用户注册
Scanner sc =new Scanner(System.in);
UserService userService =new UserServiceImpl();
while (true){
System.out.println("请输入要注册的账户名:");
String username = sc.next();
System.out.println("请输入要注册的密码:");
String password = sc.next();
try{
if(userService.register(username,password)) {
System.out.println("注册成功!");
break;
}else {
System.out.println("注册失败!");
return;
}
}catch (RuntimeException e) {
System.out.println(e.getMessage());
return;
}}
//用户登录
while (true) {
System.out.println("请登录!");
System.out.println("输入用户名:");
String userName =sc.next();
System.out.println("输入密码:");
String passWord=sc.next();
try{
if(userService.login(userName,passWord)){
System.out.println("登录成功!");
break;
}else {
System.out.println("登录失败");
continue;
}
}catch (Exception e){
System.out.println(e.getMessage()+",请重试!");
continue;
}
}
int n;
BookService bookService =new BookServiceImpl();
do{
System.out.println("---------------------------------------------------------------------------------------");
System.out.println("Welcome To Use MyBMS!");
System.out.println("请输入指定功能序号:");
System.out.println("1.查询所有图书信息 2.查询指定图书信息");
System.out.println("3.新增图书信息 4.删除图书 5.退出系统");
n = sc.nextInt();
if (n==5){
System.out.println("退出成功!");
break;
}
switch (n){
case 1:
List<Book> books = bookService.queryBooks();
if (books==null){
System.out.println("不存在任何图书信息");
break;
}
books.forEach(book -> System.out.println(book));break;
case 2:
System.out.println("请输入要查询图书的名字:");
String bookName = sc.next();
Book book = bookService.queryBookByBookName(bookName);
if (book==null){
System.out.println("图书不存在!");
break;
}
System.out.println(book);break;
case 3:
System.out.println("请输入新增图书名:");
String newBookName = sc.next();
System.out.println("请输入新增图书作者:");
String bookAuthor =sc.next();
System.out.println("请输入新增图书价格:");
double bookPrice =sc.nextDouble();
System.out.println("请输入新增图书库存:");
int bookStock=sc.nextInt();
Book newBook =new Book(null,newBookName,bookAuthor,bookPrice,bookStock);
try{
System.out.println(bookService.addBook(newBook)?"新增图书成功!":"新增图书失败!");break;
}catch (Exception e){
System.out.println(e.getMessage());break;
}
case 4:
System.out.println("请输入要删除的图书名:");
String removeBookName = sc.next();
System.out.println(bookService.removeBookByBookName(removeBookName)?"删除图书成功!":"图书不存在,删除图书失败!");
default:
System.out.println("输入功能序号不存在!请重新输入!");
}
}while (n!=5);
}
}
功能实现演示
一、注册和登陆功能
这里的注册和登陆功能的逻辑有些问题,只能先注册再登陆(当然在登陆操作时可以使用数据库t_user表中已经存在的数据);
流程:先注册 ->再登陆