JavaWeb案例

环境搭建

先创建好数据库,建表并插入数据

复制代码
create database talis;
use  talis;

-- 部门管理
create table dept(
    id int unsigned primary key auto_increment comment '主键ID',
    name varchar(10) not null unique comment '部门名称',
    create_time datetime not null comment '创建时间',
    update_time datetime not null comment '修改时间'
) comment '部门表';

insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()), (4,'就业部',now(),now()),(5,'人事部',now(),now());



-- 员工管理(带约束)
create table emp (
  id int unsigned primary key auto_increment comment 'ID',
  username varchar(20) not null unique comment '用户名',
  password varchar(32) default '123456' comment '密码',
  name varchar(10) not null comment '姓名',
  gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女',
  image varchar(300) comment '图像',
  job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管, 5 咨询师',
  entrydate date comment '入职时间',
  dept_id int unsigned comment '部门ID',
  create_time datetime not null comment '创建时间',
  update_time datetime not null comment '修改时间'
) comment '员工表';

INSERT INTO emp
	(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
	(1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
	(2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
	(3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
	(4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
	(5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
	(6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
	(7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
	(8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
	(9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
	(10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
	(11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()),
	(12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()),
	(13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()),
	(14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
	(15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
	(16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2007-01-01',2,now(),now()),
	(17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());

Properties 文件配置

复制代码
·spring.application.name=RealProject

#配置mysql的驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#配置要使用的端口号
spring.datasource.url=jdbc:mysql://localhost:3306/tlias
#配置访问数据库的用户名称
spring.datasource.username=root
#配置访问数据库的用户密码
spring.datasource.password=123456

#配置mybatis日志,将sql语句在控制台中输出
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#将下划线命名自动修改为驼峰命名
mybatis.configuration.map-underscore-to-camel-case=true

创建好对应的包

记得加上注解将类交给IOC容器处理

RES风格的URL


统一响应结果

开发流程

三层架构


复习一下

Controller

一、表现层(Presentation Layer)

功能职责:

负责与用户进行交互。这一层主要处理用户的输入和向用户展示输出结果。

包含用户界面相关的代码,如网页界面、桌面应用程序的图形用户界面(GUI)等。

接收用户的操作请求,并将这些请求传递给业务逻辑层进行处理。

技术实现:

在 Web 应用中,表现层可以使用 HTML、CSS、JavaScript 等技术来构建用户界面。服务器端的表现层可能使用模板引擎(如 Thymeleaf、JSP 等)来生成动态网页内容。

对于桌面应用程序,表现层可以使用 Java Swing、JavaFX、Qt 等图形界面开发框架。

Service

二、业务逻辑层(Business Logic Layer)

功能职责:

是软件系统的核心层,包含了系统的业务规则和逻辑处理。

负责处理来自表现层的请求,执行具体的业务操作,如数据验证、业务计算、事务处理等。

对数据访问层进行调用,获取或保存数据,但不直接与数据库或其他数据存储进行交互。

技术实现:

通常由编程语言中的类和方法组成。例如,在 Java 中,可以使用普通的 Java 类来实现业务逻辑层。

业务逻辑层的设计应该具有高内聚性,即每个模块或类都应该专注于特定的业务功能,并且与其他模块或类的耦合度要低。

Mapper

三、数据访问层(Data Access Layer)

功能职责:

负责与数据库或其他数据存储进行交互,实现数据的存储、检索、更新和删除等操作。

为业务逻辑层提供数据服务,将数据库中的数据转换为业务逻辑层可以处理的对象,或将业务逻辑层的对象持久化到数据库中。

可以封装数据库连接、SQL 执行、事务管理等底层操作,提供简单、统一的接口给业务逻辑层使用。

技术实现:

在 Java 中,可以使用 JDBC、ORM(Object-Relational Mapping)框架(如 Hibernate、MyBatis 等)来实现数据访问层。

数据访问层的设计应该考虑数据库的性能优化、数据完整性和安全性等方面的问题。

每一层都会通过注入的方式封装一个下一层的对象

定义DeptController类

复制代码
@Slf4j //可以直接调用logger中的对象进行日志的记录
@RestController
public class DeptController {

//    private static Logger log = (Logger) LoggerFactory.getLogger(DeptController.class);

    @RequestMapping("/depts")
    public Result list(){
    
        log.info("查询全部部门数据");
        //System.out.println("查询项目数据成功"); (开发时不推荐)
        //响应一个成功的结果
        return Result.success();
    };
}

利用postman发送get请求,发现最终获得了一个Json格式的数据,这是因为@RestController组合注解中包含了注解@ResponseBody,会将返回的对象变为Json格式返回。

但是当前接口利用任何的请求方式都是可以进行访问的,包括get,post,delete等,这时候需要指定method请求方法

复制代码
@RequestMapping(value = "/depts",method = RequestMethod.GET)

也可以直接用注解:@GetMapping("/depts")

这时候再使用post方式进行请求:

返回了一个 error Method Not Allowed

最后需要注入一个Service

复制代码
@Autowired
    private DeptService deptService;

实现业务逻辑层

复制代码
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptMapper deptmapper = DeptMapper.list();

    @Override
    public List<Dept> list() {
        return List.of();
    }
}

实现DAO层

复制代码
@Mapper
public interface DeptMapper {

    @Select("SELECT * from dept")
    List<Dept> list();
}

前端发送请求之后,首先请求到Controller方法,Controller方法首先调用Service来获取数据,Service又调用了Mapper接口中的方法来访问数据库,并且将查询到的结果封装到集合中,并通过Service方法交给Controller层最后返回给前端。

调用第二个重载方法,将数据传回前端 return Result.success(deptList);

最终发送Get请求获取到数据:

这里注意前面的配置文件有一点小错:配置文件里写的是tlias,而我定义数据库时,错打成了talis。这样会导致连不上数据库而返回 错误代码500

前后端联调

通过前端工程访问后端工程进行测试

通过nginx进行项目的部署,通过前端工程完成后端接口的调用最后成功获取到了数据库中的资源

总结

Delete操作

复制代码
@DeleteMapping("/depts/{id}")
    public Result deleteDept(@PathVariable  Integer id){

        deptService.delete(id);
        log.info("删除了id为" + id + "的部门");
        return Result.success();
    }

@PathVariable 这个注释表示传递的参数是路径参数

利用PostMan进行测试

在数据库和前端都可以看到成功删除了对象

新增部门

通过表单数据完成部门的新增,接收的是json数据

通过RequestBody注解进行json数据的转换

注意点

如果前端传递的数据为json格式,那么可以利用@RequestBody注解直接封装为一个对象(记得要有对应的数据名称)

复制代码
//规定以post请求来发起,新增部门
    @PostMapping("/depts")
    public Result add(@RequestBody Dept dept){
        log.info("新增部门");
        deptService.add(dept);

        return Result.success();
    }

记得在Service端进行基础数据的补全(自增或者有Default值的可以不补)

如果用postman发送请求时不加请求体,那么会有 400 报错 Bad Request,如果出现 500 报错,那么多半是访问数据库的sql语句写错了

更新操作

表现层
复制代码
//修改部门
    @PutMapping("/depts")
    public Result update(@RequestBody Dept dept){
        log.info("根据id查询并修改部门");
        deptService.list();
        deptService.update(dept);

        return  Result.success(dept);
    }
业务逻辑层
复制代码
//接口定义抽象方法
void update(Dept dept);

//类中重写
@Override
    public void update(Dept dept) {
        dept.setUpdateTime(LocalDateTime.now());
        deptmapper.update(dept);
    }
DAO层
复制代码
@Update("Update dept set name = #{name}, update_time = #{updateTime}" +
            " where id = #{id}")
    void update(Dept dept);

修改成功,返回数据正常

分页查询

sql语法

复制代码
-- 分页查询
-- limit 第一个参数 offset 起始位置  value 查询返回的数据数量
select * from emp limit 0,5;

查询结果:

上面的sql语句也意为:查询返回第一页数据,每页返回五条数据

需求:

分层解耦

分页查询(带条件)

复制代码
//分页查询员工信息和记录的条数
    //请求的数据是queryString?
    @GetMapping("/emps")
    public Result list(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize) {

        log.info("完成了分页查询");
        PageBean pageBean = empService.list(page,pageSize);

        return Result.success(pageBean);
    }

@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    EmpMapper empMapper;

    @Override
    public PageBean list(Integer page, Integer pageSize) {

        Long total = empMapper.count();

        Integer start = (page - 1) * pageSize;
        List<Emp> list = empMapper.list(start, pageSize);

        return new PageBean(total,list);
    }
}

@Mapper
public interface EmpMapper {

    @Select("SELECT count(*) from emp")
    public Long count();

    @Select("SELECT * from emp Limit #{start} , #{pagesize}")
    public List<Emp> list(Integer start, Integer pageSize);
}
PageHelper插件 针对mybatis


Page类型是PageHelp插件封装的类,所以不需要自己定义。

自动执行这条语句,并给查询语句根据给定的参数,加上limit字段

依赖引入:

在mapper接口中定义正常的Select语句

复制代码
@Select("SELECT * from emp")
    public List<Emp> list();

修改服务端代码:

复制代码
 @Override
    public PageBean list(Integer page, Integer pageSize) {

        //获取所需要的分页数据
        PageHelper.startPage(page, pageSize);

        List<Emp> list = empMapper.list();
        Page<Emp> p = (Page<Emp>) list;

        return new PageBean(p.getTotal(),p.getResult());
    }

但是最后用postman发送get请求并没有获得数据,检查了两遍也没查出错,但是代码应该没问题,讲道理...

解决了:

复制代码
2024-09-02T23:01:58.874+08:00 ERROR 256 --- [RealProject] [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.yuyu.realproject.Mapper.EmpMapper.list] with root cause

仔细查看控制台,显示list方法的sql语句没有绑定成功。idea报了个warning,设置了sql方言warning就消失了。然后再一次发起请求就成功了。此外还将list方法前的public限定词去掉了,可能是格式不对倒是sql语句没有绑定成功。

用postman发送请求并获取响应成功

通过其他条件进行查询
复制代码
select * from emp
    where name like concat('%','张','%')
          and gender = 1
          and entrydate between '2000-01-01' and '2010-01-01'
          order by update_time  desc;

直接注释掉mapper接口中的Select注释

配置xml语句:

从mybatis中午官网,入门栏中找到配置语句

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

记得修改config对应的参数,否则会报错

复制代码
<?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">

最终: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 namespace="com.yuyu.realproject.Mapper.EmpMapper">
    <select id="list" resultType="com.yuyu.realproject.Pojo.Emp">
        select *
        from emp
        <where>
            <if test="name != null">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin!=null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
            order by update_time desc
    </select>
</mapper>

Get请求成功获得数据

namespace: 后跟DAO层的函数接口

id : 跟 DAO层对应的的函数方法名

ResultType: 跟返回的单个数据类型

报500错误:

1.sql语句加了 ";"号

2.没有原始语句中的参数改为动态sql(理论上可以,但是完不成指定的功能)

3.test 里的判断条件写错了,比如begin写成了start或者entrydate

批量删除员工

请求样例:

delete的xml语句:

复制代码
<delete id="delete">
        delete from emp where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

记得foreach遍历的时候,动态sql里写的是定义的遍历对象item,在这里也就是id,而非ids。否则会导致传递参数错误

新增员工

复制代码
   @Insert("Insert into emp(username, name, gender, image, job, entrydate," +
           " dept_id, create_time, update_time) " +
           "values(#{username},#{name},#{gender},#{image}," +
           "#{job},#{entrydate},#{deptId},#{createTime}," +
           "#{updateTime})")
   void add(Emp emp);

前后端联调,注意新增员工时的部门选项来自于现存的部门,这方面后端并没有实现。

文件上传

前端程序:利用form表单和表单项。提交方式必须是post方式,文件放在请求体里

编码格式为:multipart/form-data,支持较大数据的上传,如果利用form表单默认的编码格式进行传输,仅仅只是把文件的文件名送到服务端,更改了编码格式之后:

最终文件的名称和文件的内容都会提交到服务端

前端调试的代码:

复制代码
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/html">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    姓名:<input type="text" name="username"><br>
    年龄:<input type="text" name="age"><br>
    头像:<input type="file" name="file"><br>
    <input type="submit" value="提交">
</form>
</body>
</html>

服务端的代码:

打断点可以查看到接收到的数据:(超过1Mb的文件可能进不了断点)

在调试时会产生一个临时文件,上传结束后临时文件会自动销毁。

存储接收到的临时文件
本地存储
复制代码
@Slf4j
@RestController
public class UploadControl {

    @PostMapping("/upload")
    Result upload(String username,Integer age,MultipartFile image) throws IOException {
        log.info("文件上传,{},{},{}",username,age,image);
        String filename = image.getOriginalFilename();
        //将文件存储到磁盘中
        image.transferTo(new File("E:\\file\\" + filename));

        return Result.success();
    }
}

还需要抛出IO异常,否则存文件的方法会报错.

文件成功保存到磁盘中

此外,如果使用原始文件名进行保存会出现重复,那么可能会导致前面的文件被覆盖

复制代码
//构造唯一的文件名,使用uuid
        String uuid = UUID.randomUUID().toString();

使用uuid记得拼接上文件的拓展名

拿到拓展名的方法:

复制代码
//拿到文件的拓展名
        String filename = image.getOriginalFilename();
        //先拿到.所在的位置,然后直接从这个位置往后截取
        int index = filename.lastIndexOf('.');
        String suffix = filename.substring(index);
        log.info(suffix);

直接拼接完成文件名的设置

复制代码
String newFilename = uuid + suffix;

最后上传的文件名如下

默认情况下,上传的file文件不超过1Mb,否则会报错:

传个notepad 4MB也能传

MultipartFIle类提供的方法:

但是存在本地,磁盘空间一般很难支持,而且数据安全无法保障,所以在现在的开发过程中基本不使用

云端存储


流程:

文件上传的模板代码可以在阿里云OSS的快速入门官方文档中获取。

使用AK访问云服务之前需要在环境变量中配置accesskeid和密钥,配置好重启电脑之后生效。

文件上传成功

阿里云会为每一个上传的文件配置一个url,根据这个url可以对上传的资源进行直接下载。

集成到案例中

改写工具类,直接读取环境变量之中的密钥

复制代码
@Component
public class AliOSSUtils {

    private String endpoint = "https://oss-cn-chengdu.aliyuncs.com";
    private String bucketName = "yuyub";
    /**
     * 实现上传图片到OSS
     */
    public String upload(MultipartFile file) throws IOException, ClientException {

        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 获取上传的文件的输入流
        InputStream inputStream = file.getInputStream();

        // 避免文件覆盖
        String originalFilename = file.getOriginalFilename();
        String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

        //创建ossClient对象,上传文件到 OSS
        OSS ossClient = new OSSClientBuilder().build(endpoint,credentialsProvider);
        ossClient.putObject(bucketName, fileName, inputStream);

        //文件访问路径
        String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
        // 关闭ossClient
        ossClient.shutdown();
        return url;// 把上传到oss的路径返回
    }

}

记得加上Component注解,把工具类交给IOC容器管理,这样在Controller层可以直接进行对象注入

复制代码
    @Autowired
    AliOSSUtils oss;

    @PostMapping("/upload")
    public Result upload(MultipartFile file) throws IOException, ClientException {
        log.info("文件上传到阿里云OSS,文件名{}", file.getOriginalFilename());
        String url = oss.upload(file);

        return Result.success(url);
    }

用postman发送请求,请求成功

小结:

修改员工

查询回显

查询成功

修改展示

更新的xml:

复制代码
    <update id="update">
        UPDATE emp
        <set>
        <if test="username!=null and username != '' ">
            username = #{username},
        </if>
        <if test="name!=null and name != '' ">
            name = #{name},
        </if>
        <if test="gender!=null">
            gender= #{gender},
        </if>
        <if test="image!=null">
            image = #{image},
        </if>
        <if test="job!=null">
            job=#{job},
        </if>
        <if test="deptId!=null">
            deptId=#{deptId}
        </if>
        </set>
        where id=#{id}
    </update>

记得把set关键字换成set标签

配置文件

参数配置化



yml配置文件


用yml配置文件层级更加清晰,省掉了一样的前缀的书写

成功利用yml配置文件将tomcat的默认端口设置到9000

yml基本语法:
yml数据格式

下面会显示当前的item是数组中的第几个

@ConfigurationProperties

直接利用Value注解进行注入过于繁琐:


使用条件:

1.实体类属性要和配置文件中的属性同名

2.需要给实体类提供GetSet方法

3.需要将实体类交给IOC容器管理(加上@Component注解)

4.需要指定前缀

这样程序运行时就可以自动进行注入,这样在其他地方需要使用对象的属性,只需要调用Bean对象的get方法.(记得Autowired进行注入)

configuration-processor依赖

在配置依赖时会给出相应地提示.

总结
相关推荐
我又来搬代码了2 小时前
【Android】【bug】Json解析错误Expected BEGIN_OBJECT but was STRING...
android·json·bug
CANI_PLUS11 小时前
ESP32将DHT11温湿度传感器采集的数据上传到XAMPP的MySQL数据库
android·数据库·mysql
来来走走12 小时前
Flutter SharedPreferences存储数据基本使用
android·flutter
安卓开发者13 小时前
Android模块化架构深度解析:从设计到实践
android·架构
雨白14 小时前
HTTP协议详解(二):深入理解Header与Body
android·http
阿豪元代码14 小时前
深入理解 SurfaceFlinger —— 如何调试 SurfaceFlinger
android
阿豪元代码14 小时前
深入理解 SurfaceFlinger —— 概述
android
CV资深专家16 小时前
Launcher3启动
android
stevenzqzq16 小时前
glide缓存策略和缓存命中
android·缓存·glide
雅雅姐17 小时前
Android 16 的用户和用户组定义
android