学习大数据之JDBC(使用JAVA语句进行SQL操作)(3)

文章目录

  • DBUtils工具包
    • 准备工作
    • DBUtils的介绍
    • QueryRunner
      • 空参的QueryRunner的介绍以及使用
      • 有参QueryRunner的介绍以及使用
    • ResultSetHandler结果集
      • BeanHandler<T>
      • BeanListHandler<T>
      • ScalarHander
      • ColumnListHander
  • 事务
    • 事务
      • 事务_转账分析图
      • 实现转账(不加事务)
      • 事务的介绍
      • DButils实现转账(添加事务)
      • mysql中操作事务
      • 分层思想介绍以及框架搭建
        • 转账_表现层实现
        • 转账_服务层实现
        • 转账_持久层实现
    • ThreadLocal
      • ThreadLocal基本使用和原理
      • 连接池对象管理类(小秘书)
    • 事务的特性以及隔离级别
      • 事务特性ACID
      • 并发访问问题
      • 隔离级别:解决问题
      • 演示

DBUtils工具包

准备工作

在这里插入图片描述

DBUtils的介绍

1.概述:简化jdbc开发的工具
2.三大核心对象
QueryRunner 执行sql
ResultSetHandler:处理结果集
DBUtils类: 主要用于事务管理,关闭资源等

QueryRunner

空参的QueryRunner的介绍以及使用

1.构造:QuerryRunner()
2.特点:
连接对象需要我们自己维护,自己关闭
3.方法
int update(Connection conn, String sql, Object… params)-> 针对于增删改
conn:连接对象
sql:sql语句->支持?占位符
params:为?赋值->可变参数
第一个值自动匹配sql语句中的第一个?以此类推
query(Connection conn, String sql, ResultSetHandler rsh, Object… params)->针对查询

public class test04 {
    public static void main(String[] args) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        Connection conn = DruidUtils.getConn();
        String sql = "insert into category(cname) values(?);";
        queryRunner.update(conn,sql,"服装");
        DruidUtils.close(conn,null,null);
    }
}

有参QueryRunner的介绍以及使用

1.构造:QueryRunner(DataSource ds)
2.特点:
Connection连接对象,不用我们自己管理,全部交由连接池管理
3.方法:
int update(String sql, Object… params)-> 针对于增删改
sql:sql语句->支持?占位符
params:为?赋值->可变参数
第一个值自动匹配sql语句中的第一个?以此类推
query(String sql, ResultSetHandler rsh, Object… params)->针对查询
代码示例

ResultSetHandler结果集

在这里插入图片描述

BeanHandler

1.作用:将查询出来的结果集中的第一行数据封装成javabean对象
2.方法
query(String sql, ResultSetHandler rsh, Object… params)>有参QueryRunner时使用
query(Connection conn, String sql, ResultSetHandler rsh, Object… params)->空参QueryRunner时使用
3.构造
BeanHandler(class type)
传递的是class对象其实就是我们想要封装的javabean类的class对象
将想查询出来的数据封装成哪个Javabean对象,就写哪个javabean的class对象
4.怎么理解
将查询出来的数据为javabean中的成员变量赋值

@Test
    public void beanHandler() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        Category category = queryRunner.query("select * from category",new BeanHandler<>(Category.class));
        System.out.println("category = " + category);
    }

注意在写之前需要创建一个javabean对象

public class Category {
    private int id;
    private String cname;

    public Category() {
    }

    public Category(int id, String cname) {
        this.id = id;
        this.cname = cname;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }
}

BeanListHandler

1.作用 将查询出来的结果每一条数据都封装成一个个的javabean对象,将这些javabean对象放到list集合中
2.构造
BeanListHandler(Class type)
传递的是class对象其实就是我们想要封装的javabean类的class对象
想将查询出来的数据封装成哪个javabean对象,就写哪个javabean的class对象
3.怎么理解“
将查询出来的多条数据封装成多个javabean对象,将多个javabean对象放到list集合中

public void beanListHandler() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        List<Category> list = queryRunner.query("select * from category",new BeanListHandler<>(Category.class));
        for (Category category : list) {
            System.out.println(category.getCid());
        }
    }

ScalarHander

1.作用:主要是用于处理单值的查询结果,执行的select语句,结果集只有一个
2.构造:
ScalarHandler(int columnIndex)->不常用->指定第几列
ScalarHandler(String columnName)->不常用->指定列名
ScalarHandler()->常用
3.注意:
ScalarHander和聚合函数使用更有意义

 public void scalarhander() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        Object o1 = queryRunner.query("select * from category;", new ScalarHandler<>(2));
        Object o2 = queryRunner.query("select * from category;", new ScalarHandler<>("cname"));
        Object o3 = queryRunner.query("select * from category;", new ScalarHandler<>());
        System.out.println(o1);
        System.out.println(o2);
        System.out.println(o3);
    }

ColumnListHander

1.作用 将查询出来的结果中的某一列数据,存储到list集合中
2.构造:
ColumnListHandler(int columnIndex)->指定第几列
ColumnListHandler(String columnName)->指定列名
ColumnListHandler()-> 默认显示查询结果中的第一列数据
3.注意:
ColumnListHandler可以指定泛型类型,如果不指定,返回的list泛型就是object类型

 public void columnListHandler() throws SQLException {
        QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
        List<Object> query = queryRunner.query("select * from category;", new ColumnListHandler<>(2));
        List<Object> query_1 = queryRunner.query("select * from category;", new ColumnListHandler<>());
        for (Object o : query) {
            System.out.println(o);
        }
    }

事务

事务

事务_转账分析图

在这里插入图片描述
建表

CREATE TABLE account(
	`name` VARCHAR(20),
	money INT
);

实现转账(不加事务)

public class test01 {
    public static void main(String[] args) {
        QueryRunner queryRunner = new QueryRunner();
        Connection conn = DruidUtils.getConn();
        String outMoney = "update account set money = money - 1000 where name = ?";
        String inputMoney = "update account set money = money + 1000 where name = ?";
        try {
            queryRunner.update(conn,outMoney,"王也");
            queryRunner.update(conn,inputMoney,"张楚岚");
        } catch (SQLException e) {
            e.printStackTrace();
        }finally {
            DruidUtils.close(conn,null,null);
        }
    }
}

事务的介绍

1.事务:是管理多条sql语句的一个形式,可以将多条sql看成是一组操作,是的这一组操作要么全成功,要么全失败,mysql自带事务,带式mysql的事务一次只能管理一条sql,我们需要关闭sql的自带事务,开启手动事务
2.事务操作:Connection中的方法
a.void setAutoCommit(boolean autoCommit) -> 开启手动事务
autoCommit:false -> 代表关闭mysql自带事务,开启手动事务
b.void commit()-> 提交事务 -> 如果事务一旦提交,数据将永久保存,不可逆
c.void rollback()->回滚事务-> 如果事务回滚,数据将还原到上一次改变前的数据
3.注意
如果想让事务管理多条sql语句,那么sql之间用的连接或者说事务操作作用的连接对象必须是同一条连接对象

DButils实现转账(添加事务)

相关代码块

public class test02 {
    public static void main(String[] args) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        java.sql.Connection conn1 = null;
        try {
            conn1 = DruidUtils.getConn();
            System.out.println(conn1);
            //开启事务
            conn1.setAutoCommit(false);
            String outputmoney = "update account set money = money - 1000 where name = ?";
            String inputmoney = "update account set money = money + 1000 where name = ?";
            queryRunner.update(conn1, outputmoney, "王也");
            queryRunner.update(conn1, inputmoney, "张楚岚");
            //提交事务
            conn1.commit();
            System.out.println("转账成功");
        }catch (Exception e){
            //回滚事务
            try {
                conn1.rollback();
                System.out.println("转账失败");
            }catch (SQLException ex){
                ex.printStackTrace();
            }
            e.printStackTrace();
        }finally {
            //关闭资源
            DruidUtils.close(conn1,null,null);
        }
    }
}

mysql中操作事务

1.开启事务
begin
2.提交事务
commit
3.回滚事务
rollback

#开启事务
BEGIN;
update account set money = money - 1000 where name = "王也";
update account set money = money + 1000 where name = "张楚岚";
#提交事务
COMMIT;
#回滚事务
ROLLBACK;

分层思想介绍以及框架搭建

1.表现层(web层) 和页面打交道 ---- 接收请求,回响应
2.业务层(service层) 写业务的,做计算
3.持久层(dao层) 和数据库打交道—对数据库的数据进行增删改查
在这里插入图片描述
创建package ---- 公司域名倒着写,都是小写
在这里插入图片描述

转账_表现层实现
public class AccountController {
    public static void main(String[] args) {
        //创建scanner
        Scanner scanner = new Scanner(System.in);
        System.out.println("请你输入要出钱的人");
        String outname = scanner.next();
        System.out.println("请你输入要收钱的人");
        String inname = scanner.next();
        System.out.println("请输入转账金额");
        int money = scanner.nextInt();
        //将输入的三个数据传递到service层
        AccountService accountService = new AccountService();
        accountService.transfer(outname,inname,money);
    }
}
转账_服务层实现
public class AccountService {
   public void transfer(String outname,String inname,int money){
       AccountDao accountDao = new AccountDao();
       Connection conn = DruidUtils.getConn();
       //开启事务
       try {
           conn.setAutoCommit(false);
           accountDao.outmoney(conn,outname,money);
           accountDao.inmoney(conn,inname,money);
           //提交事务
           conn.commit();
           System.out.println("转账成功");
       } catch (Exception e) {
           try {
               conn.rollback();
               System.out.println("转账失败");
           } catch (SQLException ex) {
               ex.printStackTrace();
           }
           e.printStackTrace();
       }finally {
           DruidUtils.close(conn,null,null);
       }
   }
}
转账_持久层实现
public class AccountDao {
    public void outmoney(Connection conn,String outname,int money) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        String sql = "update account set money = money - ? where name = ?";
        queryRunner.update(conn,sql,money,outname);
    }
    public void inmoney(Connection conn,String inname,int money) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        String sql = "update account set money = money + ? where name = ?";
        queryRunner.update(conn,sql,money,inname);
    }
}

1.为什么要分层写代码?
a.减低耦合度
b.每层只干自己的事儿,别的层的事儿我不干,好维护
2.考虑问题:
1.问题1:service层中干了从数据库连接池中获取连接对象,也干了事务操作;但是service不应该干这些事儿
service其实只干业务逻辑判断,业务处理
2.问题2:如果不在service层获取连接对象,那么怎么传递到dao层呢?又怎么保证service和dao层中的连接对象是同一个呢?如果不是同一个,事务管理也就废废了

3.问题解决:给service和dao层面试一个"小秘书",获取连接,事务操作,都让"小秘书"干
同时还要保证service和dao使用的连接是同一条连接

ThreadLocal

ThreadLocal基本使用和原理

1.ThreadLocal:线程管理类
2.创建:
ThreadLocal 名字 = new ThreadLocal()
3.方法:
set(数据) – 往ThreadLocal中存储数据
get() — 获取数据
4.特点
a.ThreadLocal中一次只能存储一个数据,后米的数据会把前面的数据覆盖掉
b.一个线程往ThreadLocal中存储的数据,其他线程获取不到
c.ThreadLocal底层操作了Map,key为当前正在执行的线程对象,value为我们存储的数据
当我一存,值就会和当前前程绑死,也就是说值指数已当前线程对象,其他线程对象拿不到
在这里插入图片描述

public class Demo01ThreadLocal {
    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("王也");
        System.out.println(threadLocal.get());
    }
}

连接池对象管理类(小秘书)

代码实现

public class ConnectionManager {
    private static  ThreadLocal<Connection> t1 = new ThreadLocal<Connection>();
    /*
    * 从连接池中获取连接对象,放到ThreadLocal中
    * 然后从ThreadLocal中再拿出要使用的连接对象
    * */
    public static Connection getconn(){
        //先从ThreadLocal中获取连接对象
        Connection conn = t1.get();
        if (conn == null){
            //从连接池中获取连接对象
            conn = DruidUtils.getConn();
            t1.set(conn);
        }
        return conn;
    }
    //开启事务
    public static void begin(){
        try {
            getconn().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //提交事务
    public static void commit(){
        try {
            getconn().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //回滚事务
    public static void back(){
        try {
            getconn().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //关闭连接
    public static void close(){
        try {
            getconn().close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

事务的特性以及隔离级别

事务特性ACID

1.原子性(atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2.一致性(Consistency)事务前后数据的完整性必须保持一致。
3.隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离,正常情况下数据库是做不到这一点的,可以设置隔离级别,但是效率会非常低。
4.持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

并发访问问题

如果不考虑隔离性,事务存在3中并发访问问题

  1. 脏读:一个事务读到了另一个事务未提交的数据.
  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

隔离级别:解决问题

数据库规定了四种隔离级别,分别用于描述两个事务并发的所用情况

  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
    a)存在:3个问题(脏读、不可重复读、虚读)。
    b)解决:0个问题
  2. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
    a)存在:2个问题(不可重复读、虚读)。
    b)解决:1个问题(脏读)
  3. repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
    a)存在:1个问题(虚读)。
    b)解决:2个问题(脏读、不可重复读)
    4.serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
    a)存在:0个问题。
    b)解决:3个问题(脏读、不可重复读、虚读)
    安全和性能对比
    安全性:serializable > repeatable read > read committed > read uncommitted
    性能 : serializable < repeatable read < read committed < read uncommitted
    常见数据库的默认隔离级别:
  • MySql:repeatable read
  • Oracle:read committed

演示

查询数据库的隔离级别

show variables like '%isolation%';
select @@tx_isolation;

设置数据库的隔离级别

  • set session transactionisolation level 级别字符串

  • 级别字符串:readuncommittedread committedrepeatable readserializable

  • 例如:set session transaction isolation level read uncommitted;
    读未提交:readuncommitted

  • A窗口设置隔离级别

    • AB同时开始事务
    • A 查询
    • B 更新,但不提交
    • A 再查询?-- 查询到了未提交的数据
    • B 回滚
    • A 再查询?-- 查询到事务开始前数据
  • 读已提交:read committed

    • A窗口设置隔离级别
      • AB同时开启事务
      • A查询
      • B更新、但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据改变,存在问题【不可重复读】
  • 可重复读:repeatable read

    • A窗口设置隔离级别
      • AB 同时开启事务
      • A查询
      • B更新, 但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据不变,解决问题【不可重复读】
      • A提交或回滚
      • A再查询?–数据改变,另一个事务
  • 串行化:serializable

    • A窗口设置隔离级别
    • AB同时开启事务
    • A查询
      • B更新?–等待(如果A没有进一步操作,B将等待超时)
      • A回滚
      • B 窗口?–等待结束,可以进行操作
        总结:
        我们最理想的状态是:一个事务和其他事务互不影响
        但是如果不考虑隔离级别的话,就会出现多个事务之间互相影响
        而事务互相影响的表现方式为:
        脏读
        不可重复读
        虚读/幻读

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/522998.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CTF之GET和POST

学过php都知道就一个简单传参&#xff0c;构造payload:?whatflag得到 flag{3121064b1e9e27280f9f709144222429} 下面是POST那题 使用firefox浏览器的插件Hackbar勾选POST传入whatflag flag{828a91acc006990d74b0cb0c2f62b8d8}

【网站项目】鲜花销售微信小程序

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Dubbo 服务发现

Dubbo 服务发现 1、什么是服务发现 **服务发现&#xff08;Service discovery&#xff09;**是自动检测一个计算机网络内的设备及其提供的服务。 2、Dubbo 与 服务发现 Dubbo 提供的是一种 Client-Based 的服务发现机制&#xff0c;依赖第三方注册中心组件来协调服务发现过…

【算法】两数之和(暴力求解+哈希表)

本题来源---《两数之和》。 题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里…

前端零基础学习web3开发

目录 1 钱包 2 发起交易 3 出块 4 块高 5 矿工 6 Gas费 这一节&#xff0c;我们不说让人神往的比特币&#xff0c;不说自己会不会利用这个虚拟的货币来发财&#xff0c;也不说那些模模糊糊的知识&#xff0c;什么去中心化啦&#xff0c;什么奇妙的加密啦&#xff0c;我们…

云骑士数据恢复怎么授权别的电脑

随着科技的不断发展&#xff0c;数据恢复已经成为了我们生活中不可或缺的一部分。云骑士数据恢复作为一款功能强大的数据恢复软件&#xff0c;受到了广泛的欢迎。但是&#xff0c;有时候我们需要将云骑士数据恢复授权给其他电脑使用&#xff0c;这就需要我们了解相关的操作步骤…

【XCPC笔记】2023 (ICPC) Jiangxi Provincial Contest——ABCHIJKL 做题记录

赛后gym练习及补题&#xff0c;gym链接&#xff1a;2023 (ICPC) Jiangxi Provincial Contest – Official Contest 补题顺序 L [Zhang Fei Threading Needles - Thick with Fine](https://codeforces.com/gym/104385/problem/L)题面解读参考代码 A [Drill Wood to Make Fire](h…

数字电路基础(Digital Circuit Basis )

目录 一、什么是数字电路&#xff1f; &#xff08;Digital Circuit &#xff09; 1.概念 2.分类 3.优点 4.数电与模电的区别 二、数制 (十进制&#xff1a;Decimal) 1.概述 2.进位制 3.基数 4.位权 5.二进制的算术运算 三、编码 (二进制&#xff1a;Binary ) 1.什…

Vue - 你会在同一个元素上使用v-for和v-if吗

难度级别:初级及以上 提问概率:50% 在初学者看来,v-for和v-if同时使用是非常方便的,二者共同使用的常见场景有两种。例如有两个列表,分别用于渲染学生数据和老师数据,然后有两个单选按钮,用于切换当前页面中需要展示学生列表还是老师列…

2024/4/1—力扣—不用加号的加法

代码实现&#xff1a; 思路&#xff1a;位运算&#xff0c;利用了异或和与的特性&#xff0c;异或操作与加操作的区别在于异或操作在二进制状态下两个数同1不进位&#xff0c;只是置为0&#xff0c;其他均相同&#xff0c;那么使用与运算计算进位值&#xff0c;补齐异或操作的缺…

什么是商家转账到零钱

商家转账到零钱是什么&#xff1f; 通过商家转账到零钱这个功能&#xff0c;如果我们系统需要对用户支付费用&#xff0c;比如发放佣金、提成、退款之类的&#xff0c;可以直接转账到用户的微信零钱。 【商家转账到零钱】是【企业付款到零钱】的升级版&#xff0c;2022年5月1…

windows terminal美化教程

安装terminal 微软商店下载安装terminal 配置文件 进入terminal&#xff0c;打开设置。 {"$schema": "https://aka.ms/terminal-profiles-schema",// global settings"profiles": {// profile settings"defaults": {// default sett…

LeetCode热题100:哈希

1.两数之和 题目链接&#xff1a;两数之和 题目描述&#xff1a;给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数…

蓝桥杯第十四届C++C组

三国游戏 题目描述 小蓝正在玩一款游戏。游戏中魏蜀吴三个国家各自拥有一定数量的士兵X, Y, Z (一开始可以认为都为 0 )。游戏有 n 个可能会发生的事件&#xff0c;每个事件之间相互独立且最多只会发生一次&#xff0c;当第 i 个事件发生时会分别让 X, Y, Z 增加Ai , Bi ,Ci …

fastlio2 保存每帧的点云和每帧的里程计为单独的文件做后端回环优化和手动回环优化

为了 提供数据做后端回环优化和手动回环优化,需要保存每帧的点云和每帧的里程计为单独的文件,并且需要保存的名字为ros时间戳。 效果很好,比我自己写的手动回环模块好用 // This is an advanced implementation of the algorithm described in the // following paper: /…

爬虫逆向非对称加密和对称加密案例

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; 案例--aHR0cHM6Ly9jcmVkaXQuaGxqLmdvdi5jbi94eWdzL3l6d2ZzeHF5bWQv 第一步&#xff1a;分析页面、请求…

[Win10] VMware Workstation Pro 17.5.1 Build 23298084 Win64安装教程

VMware Workstation Pro 17.5.1 Build 23298084 Win64安装教程 下载 https://download.csdn.net/download/u012621175/89088925 安装 激活 备注 如果激活不成功可以私信获取私钥

【面试经典150 | 动态规划】交错字符串

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;动态规划 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容进行…

【MYSQL之进阶篇】视图、存储过程、存储函数以及触发器

&#x1f525;作者主页&#xff1a;小林同学的学习笔录 &#x1f525;mysql专栏&#xff1a;小林同学的专栏 1.视图 1.1 定义 视图是MySQL数据库中的虚拟表&#xff0c;它基于一个或多个实际表的查询结果。视图提供了一种简单的 方法来封装和重用复杂的查询&#xff0c;同时…

基于Springboot的美术馆管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的美术馆管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…