时隔两个月的再来写博客的感觉怎么样呢,只能用“棒”来形容了。闲话少说,直接入正题,之前的博客中有说过,将spring与mybatis整个后开发会更爽,基于现在springboot已经成为整个业界开发主流框架的情况下,今天在这里就直接将mybatis整合spring boot了。
先简单地提一下Spring boot。在Mybatis还没火起来之前,大家用的是SSH(Struts2+Spring+Hibernate),之后mybatis以其小巧轻便的优点成为中小型项目的首选,与此同时基于Spring的自家mvc框架SpringMVC也火起来了,于是开发的框架又成了SSM(SpringMVC+Spring+Mybatis),但是Spring和Spring MVC本是同源,叫起来还得分开叫真是头疼,然后Spring boot出现了,它是基于Spring4的条件注册的一套快速开发整合包,同时又整合了Spring MVC了,所以说SpringMVC的那一套注解可以原封不动地搬来用。同时Spring boot为了解决Spring框架需要进行大量的配置的问题又引入自动配置的概念,也就是说能用注解我绝不用配置文件。关于Spring boot具体一点的东西我就直接在下面结合代码里讲了。
首先呢,新建一个maven项目,记得勾选上create a simple project
之后的Group id、Artifact Id之类的就可以随便填了,以下是我建好的项目结构
项目建好后第一件事,添加依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
解释一下几个关键的包
- spring-boot-starter-parent:项目可以通过继承spring-boot-starter-parent包来获得一些合理的默认配置,在在dependencies里的部分配置可以不用填写version信息,自动继承parent包的版本,当然也可以不用。
- spring-boot-starter:这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。
- spring-boot-starter-web:构建Web,包含RESTful风格框架SpringMVC和默认的嵌入式容器Tomcat,就是这个包整合了Spring mvc,同时自带嵌入式tomcat意味着我们启动项目时就只要运行main方法就行,不用再跑eclipse上自带的tomcat了
- mybatis-spring-boot-starter:这个就没什么好说的,官方提供的spring boot和mybatis的整合包。
包导完之后我的习惯是先把整个项目的包结构搭建起来
然后在最外层的包里面写启动类,老规矩先贴代码
package com.fiberhome;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
这里只有一个注解@SpringBootApplication,但是作用却大得惊人,control键然后点击该注解看源码可知它替代了@@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan这三个注解的功能。接下来解释这个三个注解的作用,理解了这三个注解,自然理解了@SpringBootApplication。
- @SpringBootConfiguration:该注解继承自@Configuration,一般与@Bean配合使用,使用这两个注解就可以创建一个简单的spring配置类,可以用来替代相应的xml配置文件。
- @EnableAutoConfiguration:该注解的意思就是
Springboot可以
根据你添加的jar包来配置你项目的默认配置,比如当你添加了mvc的jar包,它就会自动配置web项目所需的配置 - @ComponentScan:顾名思义该注解是用来扫描组件的,只要组件上有@component及其子注解@Service、@Repository、@Controller等,springboot会自动扫描到并纳入Spring 容器进行管理,有点类似xml文件中的
该注解不填属性的话就是默认扫描启动类所在的包,或者启动类所在包的下一级,所以启动类要放在最外层<context:component-scan>,
紧接着把我的实体类(User.java)代码贴出来
package com.fiberhome.pojo;
import org.springframework.stereotype.Component;
@Component
public class User {
private Long id;
private String username;
private int age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
然后我们从底层的代码写起,先是mapper接口
package com.fiberhome.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.fiberhome.pojo.User;
@Mapper
public interface UserMapper {
//获取用户名单
public List<User> getUser() throws Exception;
//根据id删除用户
public void deleteUser(int id)throws Exception;
//新增用户
public void addUser(User user)throws Exception;
}
然后是对应该mapper接口的user.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fiberhome.mapper.UserMapper">
<select id="getUser" resultType="com.fiberhome.pojo.User">
select * from user
</select>
<delete id="deleteUser" parameterType="Integer">
delete from user where id =#{id}
</delete>
<insert id="addUser" parameterType="com.fiberhome.pojo.User">
insert into user(id,username,age)values(#{id},#{username},#{age})
</insert>
</mapper>
紧接着是Service层的代码
UserService.java
package com.fiberhome.service;
import java.util.List;
import com.fiberhome.pojo.User;
public interface UserService {
//显示所有用户
public List<User>getUser()throws Exception;
//根据id删除用户
public void deleteUser(int id)throws Exception;
//新增用户
public void addUser(User user)throws Exception;
}
UserServiceImpl.java
package com.fiberhome.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.fiberhome.mapper.UserMapper;
import com.fiberhome.pojo.User;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> getUser() throws Exception {
return userMapper.getUser();
}
//根据id删除用户
@Override
public void deleteUser(int id) throws Exception {
userMapper.deleteUser(id);
}
//新增用户
@Override
public void addUser(User user) throws Exception {
userMapper.addUser(user);
}
}
最后是Controller代码
package com.fiberhome.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.fiberhome.pojo.User;
import com.fiberhome.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService userService;
@Autowired
private User user;
//显示用户
@RequestMapping("list")
public List<User> index() throws Exception {
return userService.getUser();
}
//删除用户
@RequestMapping("delete/{id}")
public String delete(@PathVariable int id) throws Exception {
userService.deleteUser(id);
return "你已经删掉了id为"+id+"的用户";
}
//增加用户
@RequestMapping("addUser")
public String addUser() throws Exception {
user.setAge(33);
user.setUsername("阿花");
userService.addUser(user);
return "增加用户";
}
}
以上代码已经写得很明白了,也没什么可解释的。值得注意的是为了将mapper装配到spring容器中去,要在mapper接口中加上@Mapper注解,或者在启动类中加上@MapperScan(“包路径”)注解。到了这里基本完工了,还差一个application.properties文件,用于存放数据库连接信息和mapper.xml文件位置
spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=123456 mybatis.mapper-locations: classpath:mapper/*.xml
运行的时候只需运行启动类的main方法即可,由于springboot嵌入了tomcat,所以项目跑起来后tomcat会自启。
到这里mybatis和springboot就整合完了,有人可能会想,既然省了这么多xml文件,有没有方法可以连user.xml文件一起省了,答案是当然有了,接下来我还会带来mybatis注解开发
spring boot整合mybatis基于注解开发以及动态sql的使用
上面讲到mybatis是怎样发挥它的作用的,主要是三类文件,第一mapper接口,第二xml文件,第三全局配置文件(application.properties),而今天我们就是来简化mybatis的工作的——利用注解替代xml配置文件。
先贴出mapper接口代码
@Mapper
public interface UserMapper {
//获取用户名单
public List<User> getUser() throws Exception;
//根据id删除用户
public void deleteUser(int id)throws Exception;
//新增用户
public void addUser(User user)throws Exception;
//修改用户信息
public void updateUser(User user) throws Exception;
}
相较于上次的代码新增了一个修改用户信息的接口,然而怎么做才能替代xml呢???
针对以上的增删改查的操作,有四个注解@Select、@Delete、@Insert、@Update,在注解中加上之前在xml中写的sql就行了,所以完整的mapper接口文件如下
@Mapper
public interface UserMapper {
//获取用户名单
@Select("select * from user")
public List<User> getUser() throws Exception;
//根据id删除用户
@Delete("delete from user where id = #{id}")
public void deleteUser(int id)throws Exception;
//新增用户
@Insert("insert into user(id,username,age)values(#{id},#{username},#{age})")
public void addUser(User user)throws Exception;
//修改用户信息
@Update("update user set username = #{name} where id = #{id}")
public void updateUser(User user) throws Exception;
}
剩下的service和controller中的代码很简单,和xml开发中的写法保持一致。需要值得注意的是不要忘记在各个接口和类中类名前的位置加上对应的注解,@Mapper、@Service、@Component等,不然spring是扫描不到的。这里有个小技巧,如果在启动类中加上注解@MapperScan(“com.XX.dao”),即可以省去@Mapper注解
说完了注解的开发我们来看一下这样的需求,如果我们要往数据库中更新一个对象,前台传过来的对象中有几个属性没有赋值,所以我们controller接收时就会将这几个属性置空,而我们需要更新不为空的属性,这时该怎么办??当然可以在service中将这个对象处理后再更新到数据库中,但是其实动态sql就可以直接解决这个问题。首先将问题分个类
if+set/where
例如现在前台将这样一个对象传过来
{ "id":1, "age":12 }
我控制器的方法为
//更新用户 public String updateUser(User user)throws Exception{ userService.updateUser(user); return "id为"+user.getId()+"的用户更新了"; }
如果我直接更新到后台显然会把这条记录之前的用户名给覆盖为空(在数据库允许该字段为空的情况下),所以看直接来看我的动态sql代码
<update id="updateUser" parameterType="com.fc.pojo.User">
update user set
<if test = "username != null">
user.username = #{username},
</if>
<if test = "age != 0">
user.age = #{age}
</if>
where id = #{id}
</update>
这样的sql拼接代码看起来很简单,但是有一个问题,如果age属性为0(前台如果不传age属性的话默认赋值为0)的话,最终sql语句会变成 update user set user.username =?,where id = ?
这样显然也是不对的,所以mybatis为我们提供了<set></set>标签将多余的逗号去掉,所以最终的动态sql变成
<update id="updateUser" parameterType="com.fc.pojo.User">
update user
<set>
<if test = "username != null">
user.username = #{username},
</if>
<if test = "age != 0">
user.age = #{age}
</if>
</set>
where id = #{id}
</update>
这样就解决了我们的问题了。同理在select语句中,也有这样的问题
<select id="getUser" parameterType="com.fc.pojo.User" resultType="com.fc.pojo.User">
select * from user where
<if test="username != null">
username=#{username}
</if>
<if test="age!= null">
and age=#{age}
</if>
</select>
如果age为空的话sql就变成了select * from user where and age=?,所以要加上<where></where>标签,这样改进后的xml就是
<select id="getUser" parameterType="com.fc.pojo.User" resultType="com.fc.pojo.User">
select * from user
<where>
<if test="username != null">
username=#{username}
</if>
<if test="age!= null">
and age=#{age}
</if>
</where>
</select>
foreach
我们在开发的时候经常会遇到这样的需求,删除多个对象,前台直接传一个对象id的集合,这个时候常规的做法是对集合处理,将id拿出来一个个删掉,其实也可以将集合放入一个包装类中,直接把包装类作为parameterType传入xml中,在xml中处理id集合。下面是包装类,我加上了数组的方式,不仅是id集合,还有id数组,都可以用这种方法来做
public class UserVo {
private List<Integer>idList;//id集合
private int[]idArray; //id数组
public List<Integer> getIdList() {
return idList;
}
public void setIdList(List<Integer> idList) {
this.idList = idList;
}
public int[] getIdArray() {
return idArray;
}
public void setIdArray(int[] idArray) {
this.idArray = idArray;
}
}
而xml文件中要使用foreach标签,在el表达式中和js中好像都有类似的用法,用来做循环操作,需要注意的是collection的值应该和包装类中的属性名保持一致。具体代码如下
<!-- 以id集合删除用户 -->
<delete id="deleteByIdList" parameterType="com.fc.pojo.UserVo">
delete from user
where id in
<foreach item="id" collection="idList" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 以id数组删除用户 -->
<delete id="deleteByIdArray" parameterType="com.fc.pojo.UserVo">
delete from user
where id in
<foreach item="id" collection="idArray" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
然后是Controller代码
//删除id集合
@RequestMapping(value="deleteByIdList",method=RequestMethod.DELETE)
public String deleteByIdList(@RequestBody UserVo userVo)throws Exception{
userService.deleteByIdList(userVo);
return "id列表里的用户都删掉了";
}
//删除id数组
@RequestMapping(value="deleteByIdArray",method=RequestMethod.DELETE)
public String deleteByIdArray(@RequestBody UserVo userVo)throws Exception{
userService.deleteByIdArray(userVo);
return "id数组里的用户都删掉了";
}
mapper接口和service的代码我就不贴了,套路跟之前都是一样的
最后是用来测试的json,第一个是测数组的,第二个测集合的
{ "idArray":[1,2,3] }
{ "idList":[1,2,3] }
sql片段
sql片段就是将一段重复率较高的sql抽取出来,供别的sql调用,这样可以有效地提高代码的复用率,话不多说,比如我们在新增一个用户时需要返回新增用户id,而我们之前的代码中已经有新增用户的sql,重复写一遍显得多此一举了,就可以把插入语句抽取出来供多次调用,代码如下,为了看到效果,我把之前的新增用户的语句也使用调用sql片段的方法来做
<sql id="base_insert_sql" >
insert into user(id,username,age)values(#{id},#{username},#{age})
</sql>
<!-- 新增用户 -->
<insert id="addUser" parameterType="com.fc.pojo.User">
<include refid="base_insert_sql" />
</insert>
<!-- 新增用户,返回用户id -->
<insert id="addUserWithId" parameterType="com.fc.pojo.User">
<selectKey keyProperty="id" resultType="int">
select LAST_INSERT_ID()
</selectKey>
<include refid="base_insert_sql" />
</insert>
好了,我在开发中常用到的动态sql就这些了,然后把以上xml文件转为注解的方法我就懒得去做了,有空再补上,最后安利一款测试软件Postman,挺好用的,公司也在用这个,以上的所有代码都是我用PostMan测试通过的,毕竟浏览器也发不了post和delete请求
--- end ---