从0开始完成SpringBoot+Mybatis实现增删改查

1、准备知识:

1)需要掌握的知识:

Java 基础,JavaWeb 开发基础,Spring 基础(没有 Spring 的基础也可以,接触过 Spring 最好),ajax,Jquery,Mybatis。

2)项目结构介绍:

  • crud-core:这里面存放的是项目的一些公共的东西,如工具类 util 包、自定义异常类、枚举类型等。
  • crud-sys:这里面主要是和数据库操作有关系的文件。实体类 entity、在没有使用 Mybatis 情况下有 repository 或 dao 实体操作类、使用 Mybatis 情况下有 Mapper 类、Service 类和 Service 的实现类都在 sys 子模块下。
  • 实体类 entity:是对应于数据库的一些实体类,是所有数据操作的基础。
  • repository 或 dao:是对实体类进行增删改查操作的类,是直接和数据库进行交互的类,这两个使用哪个都可以,完全看个人爱好。
  • Mapper:是 Mybatis 下与数据库进行交互实现实体类的操作,和 repository 和 dao 相似。
  • Service:是在前端 Controller 和后端 Dao 层之间进行协调的类,用于接收 Controller 传递过来的信息,并调用 Dao 层对数据库进行操作后返回信息给 Controller。
  • crud-web:是整个项目中唯一一个和外部网页交互的模块。包含 Controller,启动类等。
  • Controller:接收用户从前端发送的 request 请求,指定请求的路径以及返回信息给前台页面。
  • pom.xml 文件:每个项目都有自己的 pom.xml 文件,里面包含项目和父项目的信息,以及项目所依赖的所有依赖文件,在 pom.xml 文件中写入依赖后,项目会自动从本地仓库中查找需要的 jar 包,如果没有则从远程仓库中下载后存入本地仓库,这样就省去了下载 jar 包的步骤。
  • application.properties 文件:这里面写着项目中的所有配置,包括数据库的连接、mybatis 的配置,自定义配置项、项目端口等等各种配置。

 

下面开始实现一个简单的学生的添加

2、创建实体类

在 crud-sys 子项目中创建 entity 文件夹并创建和数据库相对应的 Student 类及其 get,set 函数。

public class Student {
</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 学生id </span><span style="color: rgba(0, 128, 0, 1)">*/</span>

<span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> String Name;

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 学生英语成绩 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>

<span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Integer English;

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 学生数学成绩 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>

<span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Integer Math;

</span><span style="color: rgba(0, 128, 0, 1)">/**</span><span style="color: rgba(0, 128, 0, 1)"> 学生计算机成绩 </span><span style="color: rgba(0, 128, 0, 1)">*/</span>

<span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Integer Computer;

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String getName () {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Name;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setId(String Name) {
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.Name =<span style="color: rgba(0, 0, 0, 1)"> Name;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Integer getEnglish() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> English;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setEnglish(Integer english) {
    English </span>=<span style="color: rgba(0, 0, 0, 1)"> english;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Integer getMath() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Math;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setMath(Integer math) {
    Math </span>=<span style="color: rgba(0, 0, 0, 1)"> math;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Integer getComputer() {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Computer;
}

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setComputer(Integer computer) {
    Computer </span>=<span style="color: rgba(0, 0, 0, 1)"> computer;
}

}

3、集成 Mybatis

这个项目中继承了 Mybatis,所以在进行数据库操作之前我们需要进行 Mybatis 的相关配置以及数据库的连接操作。

1)查找依赖包

进入https://mvnrepository.com/网站,输入 mybatis 查询 mybatis 依赖包:

这里选择 org.mybatis.spring.boot 里的 mybatis-spring-boot-starter:

点击之后选择版本,这里我不习惯选择太新的也不喜欢太旧的版本,所以一般会在稍微新的版本中选择用户数最多的那一个,比如 mybatis 我选择 2.0.1

点击进去之后复制依赖:

2)注入依赖并下载 jar 包

第一步之后,将文本复制到 crud-web 子项目的 pom.xml 文件中,在 <dependencies> 标签中加入刚才复制的文本。

 此时右下角出现 Import changes 和 Enable auto import,这里可以选择后者,以后修改 pom.xml 文件后,项目会自动导入 jar 包。

3)连接数据库

首先还是要通过上面的方法添加 mysql 的依赖到 pom.xml 文件中

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->

<dependency>

<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">groupId</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>mysql<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">groupId</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">artifactId</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>mysql-connector-java<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">artifactId</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

<span style="color: rgba(0, 0, 255, 1)">&lt;</span><span style="color: rgba(128, 0, 0, 1)">version</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>8.0.15<span style="color: rgba(0, 0, 255, 1)">&lt;/</span><span style="color: rgba(128, 0, 0, 1)">version</span><span style="color: rgba(0, 0, 255, 1)">&gt;</span>

</dependency>

在 crud-web 的 resource 文件夹下的 application.properties 文件中配置连接信息:

这些和普通的 web 开发中的 DBUtil 的写法大同小异。

4)配置 mybatis

同样在 application.properties 文件配置 mybatis 的映射文件路径

mybatis.mapper-locations=classpath:/mapping/*.xml

这句话的意思是 mybatis 的映射文件存在于 classpath(classpath 就是生成 target 目录后 classes 文件所在目录)的 mapping 目录下以.xml 结尾的文件。

4、编写 Mapper 类

1)创建文件

在 crud-sys 子项目下新建 mapper 文件夹,其下新建 StudentMapper 接口:

  • 这里要注意,在 Mapper 接口上需要加入 @Compent 注解,表示这是一个和 Bean 类似的实体。
  • @Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

2)编写函数

Mapper 类中主要写对实体的操作函数的定义,这里我们先定义一个添加函数。

添加代码:public void add(Student student);

5、编写映射文件 Mapping 文件

1)创建文件

在 resource 文件夹下创建 mapping 文件夹,在其下创建 StudentMapping.xml 文件。

2)添加 xml 头

在 xml 文件中写入 mybatis 映射文件需要的头,可以在网上搜索 mybatis 映射文件头文件,也可进入官网去找

<?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">

3)设置所映射的接口

设置这个 xml 文件对应的是哪个接口类中的函数。

添加 <mapper> 标签,在标签中配置 namespace 为接口类所在的路径

4)编写相应的函数所对应的 SQL

Mybatis 提供了 insert、update、delete 和 select 分别对应添加、更改、删除和查询操作,这里我们用到的是 insert 标签。

<insert id="add" parameterType="com.dingcheng365.crudsys.entity.Student">
</insert>
  • id:id 的值为所对应的 Mapper 接口中的函数的名称,必须完全一致。

  • parameterType:参数类型,如果没有参数或两个以上参数,可不写改属性,如果是一个参数,需要写上参数的类型,最好将包名加上,如 String 类型写 java.lang.String,Map 类型写 java.util.Map
    
  • resultType:这个在这个例子中没有体现。表示的是函数的返回值类型,如果为 void 可不写该属性。这里需要注意的是:resultType 表示每一个元素的值,例如我们要进行查询,返回值是 List<Student>,因为查询结果不一定是一个学生满足条件,但是这时候 resultType 需要填写的是 Student 类而非 List。
    

如果字段多的话添加语句会很长很难写,这里介绍一个快速且简单的办法:

在 Navicat 中随便选中一行,右键复制为 insert 语句:

粘贴后效果:

INSERT INTO `test`.`student` (`Name`, `English`, `Math`, `Computer`) VALUES ('zhangsan', '69', '86', '77');

这时候只需要修改后面的参数即可。

将语句粘贴到 insert 标签中,同时修改参数,使用 #{} 中间加上参数的名称:

6、编写 Service 层

1)编写 service 接口类

在 crud-sys 下创建 service 文件夹,在其下创建 StudentService 接口类,里面写入函数,一般会和 Mapper 类中的函数相似,可能会返回值不同。

2)编写 service 实现类

在 service 文件夹下创建 impl 文件夹,在其下创建 StudentServiceImpl 类实现 StudentSerivce 接口。

这里需要注意的是:Service 实现类上面需要加入 @Service 注解。在这个类中需要使用 @Autowired 注解注入 studentMapper 对象。

  • @Autowired:自动导入依赖的 bean

  • @Service:一般用于修饰 service 层的组件

然后编写代码,调用 studentMapper 的添加函数,添加成功后返回 1,如果失败则返回 0;

@Autowired
private StudentMapper studentMapper;

@Override
public int add(Student student) {
try {
studentMapper.add(student);
return 1;
}
catch (Exception e) {
return 0;
}
}

至此,crud-sys 子项目中所需要实现的功能已经编写完毕,接下来就是和网页的交互了。

7、编写页面

SpringBoot 可以识别出 resources 文件夹下的 static 及其子文件夹下的文件,如果需要自定义文件夹,则需要在配置文件中配置。

1)创建文件并写入元素

在 curd-web 子目录的 templates 文件夹下创建 addstudent.html 并写入表单元素。

 

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学生添加</title>
</head>
<body>
<form id="studentinfo">
  <table>
    <tr>
      <td>姓名:</td>
      <td><input type="text" name="name" id="name"></td>
    </tr>
    <tr>
      <td>英语成绩:</td>
      <td><input type="text" name="English" id="English"></td>
    </tr>
    <tr>
      <td>数学成绩:</td>
      <td><input type="text" name="Math" id="Math"></td>
    </tr>
    <tr>
      <td>计算机成绩:</td>
      <td><input type="text" name="Computer" id="Computer"></td>
    </tr>
    <tr>
      <td colspan="2">
        <button id="add-student-btn" type="button">提交</button>
      </td>
    </tr>
  </table>
</form>
</body>
</html>

 

2)表单验证

使用 jquery-validate 对表单输入的值进行验证,导入 jquery.min.js 和 jquery-validate.min.js 后进行编写。

// 表单验证
userInfoValidate = $('#userInfo').validate({
    rules: {
        name: {
            required: true
        },
        English: {
            required: true
        },
        Math: {
            required: true
        },
        Computer: {
            required: true
        }
    },
    messages: {
        name: {
            required: '姓名不能为空'
        },    
        English: {
            required: '英语成绩不能为空'
        },
        Math: {
            required: '数学成绩不能为空'
        },
        Computer: {
            required: '计算机成绩不能为空'
        }
    },
    errorElement: 'small',
    errorPlacement: function(error, element) {
        // 在 error 上添加 `invalid-feedback` class
        error.addClass('invalid-feedback');
    element.parent().append(error);
},
highlight: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(element) {
    $(element)
    .addClass(</span>'is-invalid'<span style="color: rgba(0, 0, 0, 1)">)
    .removeClass(</span>'is-valid'<span style="color: rgba(0, 0, 0, 1)">);
},
unhighlight: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(element, errorClass, validClass) {
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> $valid =<span style="color: rgba(0, 0, 0, 1)"> $(element);

    </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">validClass) {
       $valid.removeClass(</span>'is-invalid is-valid'<span style="color: rgba(0, 0, 0, 1)">);
    } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
        $valid.addClass(</span>'is-valid').removeClass('is-invalid'<span style="color: rgba(0, 0, 0, 1)">);
    }
},
submitHandler: </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> (form) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 确认登录时删除存储标签页对象</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> TODO: KEY值请参考`site-configs.js`文件中配置的`tabSittings`的值</span>
    window.sessionStorage.removeItem('admui.contentTabs'<span style="color: rgba(0, 0, 0, 1)">);

}

});

  • 通过 id 调用 validate 函数,示例中的 $('#userinfo').validate({..})

  • rules:表示表单验证的规则,rules 中的第一层的子元素对应的是 input 标签的 name 而非 id 值。

  • 子元素验证,在子元素中写入需要进行的验证,常用的有 required:true 表示不可为空,false 表示可为空;minlength:设置输入的最短长度;maxlength:输入的最长长度;email:true 表示必须输入正确的电子邮件;digtis:true 表示必须输入整数等。

  • message:自定义提示信息,与 rules 中一一对应,表示不满足规则时提示的信息。

  • errorElement:用什么标签标记错误,默认是 label

  • errorPlacement:自定义错误放置的位置

  • highlight:可以给未通过验证的元素加效果、闪烁等。

  • submitHandler:所有验证通过后执行的函数

效果图:

3)Ajax 实现和 Controller 的交互

$("#add-student-btn").on('click', function () {
    $.ajax({
        url: '/student/addstudent',//请求的地址
        type: 'post',//请求类型:GET、POST、DELETE、PUT
        data: { //向请求传递的数据
            "name": $("#name").val(),
            "English": $("#English").val(),
            "Math": $("#Math").val(),
            "Computer": $("#Computer").val()
        },
        async: true, //是否异步,默认为 true
        success: function (e) { //请求成功的回调函数,e 为后台传递过来的信息
            console.log(e)
        },
        error: function (e) { //请求失败的回调函数
            console.log("ajax 请求失败");}
    });});

 通过提交按钮的点击事件,向 student/addstudent 发送 POST 请求,传递的数据为用户输入的信息,请求后将后台返回的信息打印到控制台。

8、编写 Controller 层

Controller 是与前台进行直接交互的模块,所以在 crud-web 子项目中创建包 controller,在其下创建 StudentController 类。

  • Controller 类都需要加入注解 @Controller,表示这是一个控制类

  • Controller 类上面的 @RequestMapping 中的参数表示这个 Controller 中的所有映射路径都是以这个参数开头

  • 如果 Controller 中一个函数需要返回 json 数据类型,则需要加入 @ResponseBody 注解,通常用来返回 JSON 数据或者是 XML,一般我们返回字符串类型时也需要使用这个注解

  • 慎用 @RestController,@RestController 是 @ResponseBody 和 @Controller 的结合,当你返回的类型不是 json 等的时候,使用这个会将你返回的数据自动转换成 json 类型返回。比如你需要返回一个页面的时候就不能使用这个。

  • Controller 返回页面也是一个很重要的点,可以参考我的博客:https://www.cnblogs.com/guo-xu/p/11203740.html

  • 这里会发现一个错误:service 无法进行注入。原因是 service 所在的路径必须是启动类所在的路径的子目录。修改目录结构后问题得到解决。

 

@Controller
@RequestMapping("/student")
public class StudentController {
@Autowired
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> StudentService studentService;

@RequestMapping(</span>"/addstudent"<span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> String addstudent(Student student) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> String.valueOf(studentService.add(student));
}

}

 

由于前台传递数据的时候所传递的 json 对象与 Student 对象的属性一一对应,所以在 Controller 接收的时候可以直接在参数中书写 Student 对象,系统会进行自动转换。

9、控制显示页面

我们需要编写 IndexController 来实现默认页面的显示,在 controller 文件夹下创建 IndexController 文件,写入根路径的映射:

@RequestMapping("/")
public ModelAndView index() {
  return new ModelAndView("addstudent.html");}

如果发现设置完毕后启动项目显示 404 的问题,可以参考博客:https://www.cnblogs.com/guo-xu/p/11203740.html里面详细阐述了 SpringBoot 如何返回页面的问题

10、启动项目

在 CrudWebApplication.java 下右键 Run CrudWebApplication,发现启动错误:

这个问题产生的原因是虽然我们在 Mapper 类上面都加入了 @Compent 注解,但是项目并不知道有 Mapper,不知道需要去加载 Mapper,所以我们需要在启动类上方加入 @MapperScan 注解,参数为 basePackages = {"com.dingcheng365.crud.sys.mapper"},加入之后在项目启动的时候就会自动扫描这个包中的所有 Mapper 类。

@MapperScan(basePackages = {"com.dingcheng365.crud.sys.mapper"})

重新启动项目:

出现这个提示表示项目启动成功,端口号为 8080,如果想修改项目的端口号,可以修改 application.properties 文件加入 server.port= 端口号 配置项。

11、测试

启动后输入 localhost:8080,由于我们之前配置了根路径映射的函数,是返回 addstudent.html 页面,所以会出现这个页面。

输入信息后点击提交,发现添加失败,查看控制台报错:

这个问题的解决办法可参考博客:https://www.cnblogs.com/guo-xu/p/11177028.html

最简单的解决办法就是在连接语句中加入serverTimezone=GMT

加入之后重新启动并输入信息后点击提交,发现数据库中存在新添加的信息,至此学生信息添加的功能实现完毕。

实现了这个功能之后,删除、修改、查询之类的功能基本大同小异,这里我只说几点需要注意的地方。

12、其他问题

1)删除时删除单个信息

如果删除单个信息我们一般会使用 /delete/id 这样的路径,也就是直接将 id 值传递到路径中,并且不使用?id=1 这种格式,这样在 Controller 类中接收的时候有所不同,下面是实例:

@RequestMapping("/deletestudent/{id}")

@ResponseBody

public String deleteUser(@PathVariable("id")String id) {

    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> studentService.delete(id);

}

可以看到,需要加入 @PathVariable 注解,参数为需要读取的值的名称,而且在 RequestMapping 中写的路径 id 值不确定需要加上大括号。

2)删除时删除多个信息(批量删除)

批量删除的时候,我们在前台传递的肯定是一个数组,里面包含要删除的多个 id 值,这个在 Controller 接受的时候也有所不同,由于是数组的问题。下面是实例:

前台:

 

$.ajax({
    url: "/user/deleteUsers",
    type: "post",
    data: {
        ids": ids
    },
    success: function (e) {
        if (e == "success") {
           toastr.success("删除成功");} else {
            toastr.error("删除失败,请稍后重试");}
    },
    error: function (e) {console.log(e);
    }
});

后台接收:

@RequestMapping(value = "/deleteUsers")
@ResponseBody
public String deleteUsers(@RequestParam("ids[]") List<String> ids,)
{……}

在进行数组接受的时候,需要在 @RequestParam 注解中加入名称加上 [] 的参数才能正确接收。

3)前台传递过来的对象并没有和后台相对应

这个实例中,我们从前台接收的对象的参数正好和后台存在的 Student 类相对应,所以我们在后台直接用 Student 对象接受,那如果不一致的时候该怎么办?有两种情况:

  • 参数少的时候

当参数个数比较少,2-4 个左右时,我们可以直接在接收的函数中一个一个罗列出来,对应关系使用 @RequestParam 来进行区分。

  • 参数多的时候

当参数个数比较多时,我个人推荐一个类型——Map 类,这个类真的是百用不爽,由于他和 json 结构上的相似性,导致这个类的用途非常之大。在进行数据查询的时候如果我们要查询的结果并不是某一个类的集合,这个时候用 Map 对象也可以很完美的解决问题。