本人博客:玖玖的个人博客 (zhangxi.online),欢迎大家来踩
该文章原地址:
JavaSE+JDBC进行控制台输出的客户管理系统! (实训/课堂实践推荐) (zhangxi.online)
本人诚挚的特别感谢:尚硅谷/黑马程序员提供的学习案例
项目介绍:
利用JavaSE技术,进行控制台输出的客户管理系统! 主要功能让包含客户展示,客户删除,客户添加,客户修改,退出系统!
实践前置要求:
- JAVASE基础
- MySQL基础
- JDBC基础
项目地址:
Git:JDBC-CMS: 控制台输出的客户管理系统! 主要功能让包含客户展示,客户删除,客户添加,客户修改,退出系统!
如果不会克隆下载,没关系,下面会一步一步的慢慢引导各位搭建及敲一遍代码。
项目截图:
现在开始进入正题
一、回顾JDBC-API
API的基本使用步骤:1、注册驱动【依赖的jar包 进行安装】 ----> 2、获取连接【connection建立连接】 ----> 3、创建发送sql语句对象【statement 创建发送sql语句的statement】(小车) ----> 4、发送sql语句,并获取返回结果【statement发送sql语句到数据库 并且取得返回结构】 ----> 5、结果集解析【将result结果解析出来】 ----> 6、资源关闭【释放resultset、statement、connection】
其中我们可以称第4步statement为一个发车的过程,使用 statement.addBatch();
还可以模拟小车装很多的货物(批量数据插入),最终使用statement.executeBatch();
完成发车! 批量操作!
- 但这样会造成每次都通过DriverManager获取新连接,用完直接抛弃断开, 连接的利用率太低,太浪费。
- 对于数据库服务器来说,压力太大了。我们数据库服务器和Java程序对连接数也无法控制,很容易导致数据库服务器崩溃。
我们可以建立一个连接池,这个池中可以容纳一定数量的连接对象,一开始, 我们可以先替用户先创建好一些连接对象,等用户要拿连接对象时,就直接从池中拿,不用新建了,这样也可以节省时间。然后用户用完后,放回去,别人可以接着用。
可以提高连接的使用率。当池中的现有的连接都用完了,那么连接池可以向服务器申请新的连接放到池中。
JDBC 的数据库连接池使用 javax.sql.DataSource接口进行规范,所有的第三方连接池都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收,其中Druid是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,妥妥国货之光!!!!
所以本案例使用的是Druid。
二、搭建项目
新建项目
虽说勾选到了Maven,但本项目暂未使用到Maven,可以忽略。
新建目录lib用于存放Jar包
1、引入两个Jar包
- druid-1.1.21.jar
- mysql-connector-java-8.0.27-bin.jar
推荐3种方式引入:
1、可从Git上扒我上面上传的包下来,复制到自己的项目中。
2、可以直接从Maven中直接下载。(需要Maven基础)
3、也可以从官网下载适合自己的版本。(百度就能找到)
2、创建dao、service、javabean、view、util包
- dao-做数据交互,实现对数据库的操作(增、删、改、查)
- service-服务层,专注于业务逻辑,对数据库的操作则需要借助 Dao 层实现
- javabean-实体类
- view-做视图界面
- util-存放一堆工具类
3、添加数据库相关配置
-- 员工表
CREATE TABLE t_customer(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客户主键',
NAME VARCHAR(20) COMMENT '客户名称',
gender VARCHAR(4) COMMENT '客户性别',
age INT COMMENT '客户年龄',
salary DOUBLE(8,1) COMMENT '客户工资',
phone VARCHAR(11) COMMENT '客户电话')
4、在resources添加druid.properties
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
username=root
password=root
url=jdbc:mysql:///zhangxi
三、创建工具类(在util包下)
1、创建JDBCTools
这个工具类的作用就是用来给所有的SQL操作提供“连接”,和释放连接。
创建变量DataSource与ThreadLocal
private static DataSource ds;
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
- 这里使用ThreadLocal的目的是为了让同一个线程,在多个地方getConnection得到的是同一个连接。
- 这里使用DataSource的目的是为了(1)限制服务器的连接的上限(2)连接的重用性等
静态代码块
static {
try {
Properties properties = new Properties();
properties.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
获取连接方法
public static Connection getConnection() throws Exception{
Connection connection = tl.get();
if (connection ==null ){//当前线程还没有拿过连接,就给它从数据库连接池拿一个
connection = ds.getConnection();
tl.set(connection);
}
return connection;
}
释放连接方法
public static void free() throws Exception{
Connection connection = tl.get();
if (connection != null){
tl.remove();
connection.setAutoCommit(true);//避免还给数据库连接池的连接不是自动提交模式(建议)
connection.close();
}
}
2、完整的JDBCTools代码
package com.zhangxi.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Properties;
/**
* ClassName: JDBCTools
* Package: com.zhangxi
*/
/*
这个工具类的作用就是用来给所有的SQL操作提供“连接”,和释放连接。
这里使用ThreadLocal的目的是为了让同一个线程,在多个地方getConnection得到的是同一个连接。
这里使用DataSource的目的是为了(1)限制服务器的连接的上限(2)连接的重用性等
*/
public class JDBCTools {
private static DataSource ds;
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
static {
try {
Properties properties = new Properties();
properties.load(ClassLoader.getSystemResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
}catch (Exception e){
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception{
Connection connection = tl.get();
if (connection ==null ){//当前线程还没有拿过连接,就给它从数据库连接池拿一个
connection = ds.getConnection();
tl.set(connection);
}
return connection;
}
public static void free() throws Exception{
Connection connection = tl.get();
if (connection != null){
tl.remove();
connection.setAutoCommit(true);//避免还给数据库连接池的连接不是自动提交模式(建议)
connection.close();
}
}
}
1、创建BaseDao
主要涉及两个方法
1.通用的增、删、改的方法
创建update方法,涉及两个参数String sql:sql;Object... args:给sql中的?设置的值列表,可以是0~n
public int update(String sql, Object... args) throws Exception {
}
创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
设置?的值
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
执行sql
int len = preparedStatement.executeUpdate();
preparedStatement.close();
没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
2.通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
这里的clazz接收的是T类型的Class对象, 如果查询员工信息,clazz代表Employee.class, 如果查询部门信息,clazz代表Department.class,
public <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) throws Exception {
}
创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
设置?的值
if (args.length > 0 && args != null) {
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[1]);
}
}
执行sql
ResultSet resultSet = preparedStatement.executeQuery();
获取结果集的元数据对象
ArrayList<T> arrayList = new ArrayList<>();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int count = resultSetMetaData.getColumnCount();
遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中
while (resultSet.next()) {
//循环一次代表有一行,代表有一个T对象
T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
//把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
for (int i = 1; i <= count; i++) {
//for循环一次,代表取某一行的1个单元格的值
Object value = resultSet.getObject(i);
//这个值应该是t对象的某个属性值
//获取该属性对应的Field对象
//这里再取别名可能没办法对应上
String columnName = resultSetMetaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这么做可以操作private的属性
field.set(t, value);
}
arrayList.add(t);
}
resultSet.close();
preparedStatement.close();
没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
2、完整的BaseDao代码
package com.zhangxi.util;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
/**
* ClassName: BaseDao
* Package: com.zhangxi
*/
public class BaseDao {
/*
通用的增、删、改的方法
String sql:sql
Object... args:给sql中的?设置的值列表,可以是0~n
*/
public int update(String sql, Object... args) throws Exception {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置?的值
if (args != null && args.length > 0) {
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
}
}
//执行sql
int len = preparedStatement.executeUpdate();
preparedStatement.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return len;
}
/*
通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
这里的clazz接收的是T类型的Class对象,
如果查询员工信息,clazz代表Employee.class,
如果查询部门信息,clazz代表Department.class,
*/
public <T> ArrayList<T> query(Class<T> clazz, String sql, Object... args) throws Exception {
// 创建PreparedStatement对象,对sql预编译
Connection connection = JDBCTools.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置?的值
if (args.length > 0 && args != null) {
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[1]);
}
}
//执行sql
ResultSet resultSet = preparedStatement.executeQuery();
/*
获取结果集的元数据对象。
元数据对象中有该结果集一共有几列、列名称是什么等信息
*/
ArrayList<T> arrayList = new ArrayList<>();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int count = resultSetMetaData.getColumnCount();
//遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
while (resultSet.next()) {
//循环一次代表有一行,代表有一个T对象
T t = clazz.newInstance();//要求这个类型必须有公共的无参构造
//把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
for (int i = 1; i <= count; i++) {
//for循环一次,代表取某一行的1个单元格的值
Object value = resultSet.getObject(i);
//这个值应该是t对象的某个属性值
//获取该属性对应的Field对象
//这里再取别名可能没办法对应上
String columnName = resultSetMetaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);//这么做可以操作private的属性
field.set(t, value);
}
arrayList.add(t);
}
resultSet.close();
preparedStatement.close();
//这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
//没有开启事务的话,直接回收关闭即可!
if (connection.getAutoCommit()) {
//回收
JDBCTools.free();
}
return arrayList;
}
public <T> T queryBean(Class<T> clazz, String sql, Object... args) throws Exception {
ArrayList<T> arrayList = query(clazz,sql,args);
if (arrayList == null || arrayList.size() ==0){
return null;
}
return arrayList.get(0);
}
}
四、创建service层
1、完整代码
package com.zhangxi.cms.service;
import com.zhangxi.cms.dao.CustomerDao;
import com.zhangxi.cms.javabean.Customer;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* 这是一个具有管理功能的功能类. 内部数据不允许外部随意修改, 具有更好的封装性.
*/
public class CustomerService {
private CustomerDao customerDao = new CustomerDao();
/**
* 用途:返回所有客户对象
* 返回:集合
*/
public List<Customer> getList() {
try {
return customerDao.queryList();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 用途:添加新客户
* 参数:customer指定要添加的客户对象
*/
public void addCustomer(Customer customer) {
try {
customerDao.insertCustomer(customer);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
/**
* 用途:返回指定id的客户对象记录
* 参数: id 就是要获取的客户的id号.
* 返回:封装了客户信息的Customer对象
*/
public Customer getCustomer(int id) {
try {
return customerDao.queryById(id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 修改指定id号的客户对象的信息
* @param id 客户id
* @param cust 对象
* @return 修改成功返回true, false表明指定id的客户未找到
*/
public boolean modifyCustomer(int id, Customer cust) {
int rows = 0;
try {
rows = customerDao.updateCustomer(cust);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return rows > 0;
}
/**
* 用途:删除指定id号的的客户对象记录
* 参数: id 要删除的客户的id号
* 返回:删除成功返回true;false表示没有找到
*/
public boolean removeCustomer(int id) {
int rows = 0;
try {
rows = customerDao.deleteCustomer(id);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return rows > 0;
}
}
五、创建dao层
1、完整代码
package com.zhangxi.cms.dao;
import com.zhangxi.cms.javabean.Customer;
import com.zhangxi.cms.utils.BaseDao;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* @Author 赵伟风
* Description: 客户进行数据库操作的类
*/
public class CustomerDao extends BaseDao {
public List<Customer> queryList() throws Exception {
ArrayList<Customer> list = query(Customer.class, "select * from t_customer");
return list;
}
public void insertCustomer(Customer customer) throws SQLException {
int rows = update("insert into t_customer(name,gender,age,salary,phone) values (?,?,?,?,?)",
customer.getName(), customer.getGender(),customer.getAge(),customer.getSalary(),customer.getPhone());
}
public Customer queryById(int id) throws Exception {
Customer customer = queryBean(Customer.class, "select * from t_customer where id = ?", id);
return customer;
}
public int deleteCustomer(int id) throws SQLException {
return update("delete from t_customer where id =?", id);
}
public int updateCustomer(Customer cust) throws SQLException {
return update("update t_customer set name = ? , gender = ? , age = ? ," +
"salary = ? , phone = ? where id = ? ;", cust.getName(), cust.getGender(),
cust.getAge(), cust.getSalary(), cust.getPhone(), cust.getId());
}
}
六、创建view层
涉及两个类CustomerView、KeyboardUtility,根据注释进行参考
1、KeyboardUtility
package com.zhangxi.cms.view;
import java.util.Scanner;
public class KeyboardUtility {
private static Scanner scanner = new Scanner(System.in);
public static void readReturn() {
System.out.print("按回车键继续...");
readKeyBoard(100, true);
}
public static char readMenuSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false);
c = str.charAt(0);
if (c != '1' && c != '2' &&
c != '3' && c != '4' && c != '5') {
System.out.print("选择错误,请重新输入:");
} else break;
}
return c;
}
public static char readChar() {
String str = readKeyBoard(1, false);
return str.charAt(0);
}
public static char readChar(char defaultValue) {
String str = readKeyBoard(1, true);
return (str.length() == 0) ? defaultValue : str.charAt(0);
}
public static int readInt() {
int n;
for (; ; ) {
String str = readKeyBoard(8, false);
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
public static int readInt(int defaultValue) {
int n;
for (; ; ) {
String str = readKeyBoard(8, true);
if (str.equals("")) {
return defaultValue;
}
try {
n = Integer.parseInt(str);
break;
} catch (NumberFormatException e) {
System.out.print("数字输入错误,请重新输入:");
}
}
return n;
}
public static String readString(int limit) {
return readKeyBoard(limit, false);
}
public static String readString(int limit, String defaultValue) {
String str = readKeyBoard(limit, true);
return str.equals("")? defaultValue : str;
}
public static char readConfirmSelection() {
char c;
for (; ; ) {
String str = readKeyBoard(1, false).toUpperCase();
c = str.charAt(0);
if (c == 'Y' || c == 'N') {
break;
} else {
System.out.print("选择错误,请重新输入:");
}
}
return c;
}
private static String readKeyBoard(int limit, boolean blankReturn) {
String line = "";
while (scanner.hasNextLine()) {
line = scanner.nextLine();
if (line.length() == 0) {
if (blankReturn) return line;
else continue;
}
if (line.length() < 1 || line.length() > limit) {
System.out.print("输入长度(不大于" + limit + ")错误,请重新输入:");
continue;
}
break;
}
return line;
}
}
2、CustomerView
package com.zhangxi.cms.view;
import com.zhangxi.cms.javabean.Customer;
import com.zhangxi.cms.service.CustomerService;
import java.util.List;
/**
* 这是主控模块, 负责菜单显示和用户交互. 也称为UI, 内部要频繁到管理器对象, 所以使用对象关联
*/
public class CustomerView {
/**
* 关联到的管理器对象
*/
private CustomerService customerService = new CustomerService();
/**
* 进入主菜单, 是项目的真正入口, 不可以轻易结束
*/
public void enterMainMenu() {
// 1) 声明布尔
boolean loopFlag = true;
// 2) 写循环
do {
System.out.println("\n--------------------------------客户信息管理--------------------------------------\n");
listAllCustomers();
System.out.print("1 添加客户 2 修改客户 3 删除客户 4 客户列表 5 退 出 请选择(1 - 5) : ");
// 读取用户选择
char choice = KeyboardUtility.readMenuSelection();
switch (choice) {
case '1' : addNewCustomer(); break;
case '2' : modifyCustomer(); break;
case '3' : deleteCustomer(); break;
case '4' : listAllCustomers(); break;
case '5' :
System.out.print("确认是否退出(Y/N) : ");
// 获取用户输入的确认
char confirm = KeyboardUtility.readConfirmSelection();
if (confirm == 'Y') {
loopFlag = false;
}
break;
}
} while (loopFlag);
}
/**
* 添加新员工
*/
private void addNewCustomer() {
Customer customer = new Customer();
System.out.println("---------------------添加客户---------------------");
System.out.print("姓名 : ");
String name = KeyboardUtility.readString(10);
customer.setName(name);
System.out.print("性别 : ");
String gender = KeyboardUtility.readString(1);
customer.setGender(gender);
System.out.print("年龄 : ");
int age = KeyboardUtility.readInt();
customer.setAge(age);
System.out.print("工资 : ");
int salary = KeyboardUtility.readInt();
customer.setSalary(salary);
System.out.print("电话 : ");
String phone = KeyboardUtility.readString(15);
customer.setPhone(phone);
// 通过调用管理器对象完成 员工添加
customerService.addCustomer(customer);
System.out.println("---------------------添加完成---------------------");
}
/**
* 修改员工
*/
private void modifyCustomer () {
System.out.println("---------------------修改客户---------------------");
System.out.print("请选择待修改客户ID(-1退出) : ");
// 获取用户输入的id
int id = KeyboardUtility.readInt();
if (id == -1) {
return;
}
// 根据编号定位要修改的目标对象
Customer target = customerService.getCustomer(id);
if (target == null) {
System.out.println("--------------指定ID[" + id + "]的客户不存在-----------------");
return;
}
System.out.println("<直接回车表示不修改>");
System.out.print("姓名(" + target.getName() + ") : ");
String name = KeyboardUtility.readString(10, target.getName());
target.setName(name);
System.out.print("年龄(" + target.getAge() + ") : ");
int age = KeyboardUtility.readInt(target.getAge());
target.setAge(age);
System.out.print("工资(" + target.getSalary() + ") : ");
int salary = KeyboardUtility.readInt((int) target.getSalary());
target.setSalary(salary);
System.out.print("电话(" + target.getPhone() + ") : ");
String phone = KeyboardUtility.readString(15, target.getPhone());
target.setPhone(phone);
customerService.modifyCustomer(id, target);
System.out.println("---------------------修改完成---------------------");
}
/**
* 删除员工
*/
private void deleteCustomer () {
System.out.println("---------------------删除客户---------------------");
System.out.print("请选择待删除客户ID(-1退出) : ");
// 获取用户输入的ID
int id = KeyboardUtility.readInt();
if (id == -1) {
return;
}
System.out.print("确认是否删除(Y/N) : ");
// 获取用户输入的确认
char confirm = KeyboardUtility.readConfirmSelection();
if (confirm == 'Y') {
boolean flag = customerService.removeCustomer(id);
if (flag) {
System.out.println("---------------------删除完成---------------------");
} else {
System.out.println("--------------指定ID[" + id + "]的客户不存在-----------------");
}
}
}
/**
* 员工列表
*/
private void listAllCustomers() {
System.out.println("---------------------------------客户列表--------------------------------------");
// 真的获取所有员工
List<Customer> list = customerService.getList();
if (list == null || list.size() == 0) {
System.out.println("没有数据, 请添加新数据...");
} else {
System.out.println("ID\t姓名\t\t性别\t\t年龄\t\t工资\t\t\t电话");
for (Customer customer : list) {
System.out.println(customer);
}
}
System.out.println("-----------------------------------------------------------------------------");
}
}
七、Main方法调用
package com.zhangxi.cms.main;
import com.zhangxi.cms.view.CustomerView;
public class CustomerManage {
public static void main(String[] args) {
CustomerView view = new CustomerView();
view.enterMainMenu();
}
}
大功告成!!!!!