目录
- 引出
- mybatis框架使用
- 1.导包:mybatis包+分页的包pom.xml文件【war包】
- 1.导包:mybatis包+分页的包pom.xml文件【jar包】
- 2.resources下配置mybatis-config.xml文件,以及log4j.properties文件
- 3.在resources下建文件
- 4.在UserMapper中写SQL
 
- mybatis项目应用
- 1.封装SqlSessionUtils.java工具类
- 2.多条件查询<where, if>,传入Boolean
- 3.多条件查询IN,foreach+可变长度参数<choose,when,otherwise>,传入list
- 4.多条件查询进阶版<where,choose,when,otherwise,foreach,if>
- 5.传入Boolean类型的可变长度参数
- 6.多个条件排序 ORDER BY num,unit
- 7.一条新增影响多个数据库的表@Transactional
 
- mybatis使用报错集锦
- 1.忘记加 resultType="opus"
- 2.前端显示list页面数据为空
- 3.由于mybatis版本引起的问题
- 可以在这里看相关的包
 
- 4. but found: 11:返回值类型错误
- 5.Mapper.XML文件中的id重复
- 6.删除不生效:Mapper.xml文件中的<delete写成了select
- 7.修改数据:报500异常,update 的逗号,没加
- 排查bug的顺序:
 
- 8.xml文件和java文件名不一致:
- 9.在xml文件中的resultType写错映射对象
- 10.日期类型判断时加了!=''引发的异常
 
- 总结
引出
1.mybatis可以实现SQL语句和Java代码的解耦;
 2.mybatis使用,tomcat的war包,和spring的jar包,分页的拦截器;
 3.mybatis项目应用初步,SqlSessionUtils工具类,泛型的方式;
 4.多条件查询<where,choose,when,otherwise,foreach,if>,排序order;
 5.事务回滚,要么都成功,要么都失败,@Transactional注解;
 6.在tomcat和spring框架下,常见的报错集锦
mybatis框架使用
https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api

1.导包:mybatis包+分页的包pom.xml文件【war包】
mybatis的包,日志相关的包,分页相关的包,
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.tianju</groupId>
  <artifactId>javawebHome0529</artifactId>
  <packaging>war</packaging>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>
    <!--    jsp相关-->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>javax.servlet.jsp-api</artifactId>
      <version>2.3.3</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <!--    数据库相关-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.20</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.16</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.22.RELEASE</version>
    </dependency>
    <!--    其他-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.22</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.5</version>
    </dependency>
    <!--    工具包-->
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.8.11</version>
    </dependency>
    <!--    fastjson包-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.76</version>
    </dependency>
    
    <!--   mybatis 框架-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.5</version>
    </dependency>
    <!--        分页相关的包-->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>3.7.5</version>
    </dependency>
    <!--    日志相关的包-->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
    </dependency>
  </dependencies>
</project>
1.导包:mybatis包+分页的包pom.xml文件【jar包】
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>communityManager</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--    项目打包成jar包-->
    <packaging>jar</packaging>
    <!--    项目描述-->
    <description>物业管理v1.0版本</description>
    <!--    继承一个父-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <version>2.3.0.RELEASE</version>
        <artifactId>spring-boot-starter-parent</artifactId>
    </parent>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <!--    做web项目的包-->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--       前端模板引擎,功能类似于jsp-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--        分页工具,匹配springBoot的jar包-->
        <!--        后面加spring-boot-starter,表示按照spring重新改写的版本-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!--        mybatis的包-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <!--        druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--    工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.11</version>
        </dependency>
<!--        commons-io-->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.2</version>
        </dependency>
<!--        阿里云短信验证码相关包-->
        <dependency>
            <groupId>com.aliyun</groupId>
            <artifactId>aliyun-java-sdk-core</artifactId>
            <version>4.5.3</version>
        </dependency>
<!--        fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
<!--        增强方法的包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>
<!--        测试sql mapper 的包-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>
    </dependencies>
    <!--    构建-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
application.yml配置文件
server:
  port: 82
# 1.连接数据库——对应之前 xml文件的数据库
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/community?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
    username: root
    password: 123
  ## TODO:设置上传文件大小
  servlet:
    multipart:
      max-file-size: 10MB # 设置单个文件最大大小为10MB
# mybatis其他配置
mybatis:
  # 2.给实体类起别名,首字母小写
  type-aliases-package: com.tianju.entity
  configuration:
    # 3.开启驼峰命名
    map-underscore-to-camel-case: true
    # 4.让日志生效
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 5.扫描sql的位置
  mapper-locations: classpath:/mapper/*Mapper.xml
# 全局的一个路径,用@Values获取
repairImg: D:/620/repair/
# 日志的相关配置
logging:
  file:
    name: D:\\620\\log\\community.log
  level:
    org.springframework.web: debug
    com.tianju: debug
    org.springframework.jdbc.support.JdbcTransactionManager: debug
2.resources下配置mybatis-config.xml文件,以及log4j.properties文件

my-batis-cofig.xml
要点:
(1)开启驼峰命名;(2)日志生效;(3)给所有实体类起别名;(4)分页工具PageHelper要加拦截器;(5)配置连接数据库相关的driver,url,username,password;其中url需要在&后面加 amp;
(6)配置mapper,采用简化的方式,用<package,点的方式
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 数据库和实体类 驼峰的问题 :开启驼峰命名-->
    <settings>
<!--        处理驼峰命名-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
<!--        日志生效-->
        <setting name="logImpl" value="LOG4J"/>
    </settings>
<!--    别名 给所有的实体类都起一个别名com.tianju.entity,所有别名都是类名的首字母小写-->
    <typeAliases>
        <package name="com.tianju.entity"/>
    </typeAliases>
<!--    分页需要加个拦截器-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageHelper">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123"/>
            </dataSource>
        </environment>
    </environments>
<!--    告诉配置文件,sql的xml文件在哪,java找用.  普通文件用 /-->
    <mappers>
<!--        简化方式,不然得写很多mapper-->
<!--        <mapper resource="com/tianju/dao/UserMapper.xml"/>-->
        <package name="com.tianju.dao"/>
    </mappers>
</configuration>
log4j.properties文件:
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
#log4j.rootCategory=debug, CONSOLE, LOGFILE
log4j.rootCategory=debug, CONSOLE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=D:/axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
3.在resources下建文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 指定xml文件和 项目中哪个接口对应-->
<mapper namespace="com.tianju.dao.ComUserMapper">
</mapper>

4.在UserMapper中写SQL
要点:
(1)update,delete,insert不要写成select,不然会出现删除数据后,数据库确实删除了,但是前端list还有;
(2)if语句中不要忘记加逗号:
(3)select语句中,要记得加返回值;
mybatis项目应用
1.封装SqlSessionUtils.java工具类
要点:
- 读取mybatis配置文件,告诉那里读取;
- 生成一个建造sqlSession工厂的建造者,SqlSessionFactoryBuilder;
- 建造者SqlSessionFactoryBuilder根据mybatis-config.xml建造一个SqlSession工厂,SqlSessionFactory;
- 在SqlSession工厂中,通过SqlSessionFactory生成一个sqlSession,且打开自动提交sqlSession = sqlSessionFactory.openSession(true);
- 需要什么类型的mapper就提供什么类型的mapper,public static <T> T getMapper(Class<T> mapperClass){return sqlSession.getMapper(mapperClass);};
getMapper方法—泛型T
D:\javalearn\思维导图笔记\mdPictures\image-20230614104920990.png


package com.tianju.util;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class SqlSessionUtils {
    private static SqlSession sqlSession;
    static {
        //1.读取mybatis配置文件
        InputStream is = null;
        try {
            is = Resources.getResourceAsStream("mybatis-config.xml");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //2.生成一个工厂--这个工程是通过建造者建造出来的
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3.建造者根据mybatis-config.xml建造一个SqlSession工厂
        SqlSessionFactory sqlSessionFactory = builder.build(is);
        //4.通过sqlSessionFactory生成一个sqlSession
        sqlSession = sqlSessionFactory.openSession(true);
        //如果设置为true,每次自动提交
    }
    public static <T> T getMapper(Class<T> mapperClass){
        return sqlSession.getMapper(mapperClass);
    }
}
2.多条件查询<where, if>,传入Boolean

@Mapper
public interface HouseMapper {
    List<House> queryList(
            @Param("buildingId") Integer buildingId, // 楼栋
            @Param("storey") Integer storey, // 楼层
            @Param("roomNum") String roomNum, // 房间号
            @Param("areaMin") Double areaMin, // 面积
            @Param("areaMax") Double areaMax, // 面积
            @Param("intoDateBegin") Date intoDateBegin, // 入住开始时间
            @Param("intoDateEnd") Date intoDateEnd, // 入住结束时间
            @Param("areaOrderASC") Boolean areaOrderASC,
            @Param("areaOrderDESC") Boolean areaOrderDESC// 排序
                                      );
<mapper namespace="com.tianju.dao.HouseMapper">
    <select id="queryList" resultType="house">
<!--        SELECT-->
<!--        c_owner_house.*,-->
        SELECT
        c_house.id,
        c_owner_house.ownerId,
        c_building.num,c_building.floors,c_building.unit,
        c_house.storey,c_house.roomNum,c_house.area,c_house.into_date,c_house.status,c_house.building_id,
        user_owner.realname AS ownerName
        FROM c_house
        LEFT JOIN c_building ON c_building.id = c_house.building_id
        LEFT JOIN c_owner_house ON c_owner_house.houseId = c_house.id
        LEFT JOIN user_owner ON user_owner.id = c_owner_house.ownerId
        <where>
            <if test="buildingId!=null">
                c_building.id=#{buildingId}
            </if>
            <if test="storey!=null">
                AND c_house.storey=#{storey}
            </if>
            <if test="areaMin!=null">
                AND c_house.area >= #{areaMin}
            </if>
            <if test="areaMax!=null">
                AND c_house.area <= #{areaMax}
            </if>
            <if test="intoDateBegin!=null">
                AND c_house.into_date > #{intoDateBegin}
            </if>
            <if test="intoDateEnd!=null">
                AND c_house.into_date <= #{intoDateEnd}
            </if>
            <if test="roomNum!=null and roomNum!=''">
                AND roomNum LIKE #{roomNum}
            </if>
        </where>
        <if test="areaOrderASC">
            ORDER BY c_house.area
        </if>
        <if test="areaOrderDESC">
            ORDER BY c_house.area DESC
        </if>
    </select>
service层使用
    @Autowired
    private HouseMapper houseMapper;
    @Override
    public PageInfo<House> queryList(Integer buildingId, Integer storey, String roomNum, Double areaMin, Double areaMax, Date intoDateBegin, Date intoDateEnd,
                                     Integer pageNum, Integer pageSize,
                                     Boolean areaOrderASC, Boolean areaOrderDESC) {
        PageHelper.startPage(pageNum,pageSize);
        List<House> list = houseMapper.queryList(buildingId,storey,roomNum,areaMin,areaMax,intoDateBegin,intoDateEnd,areaOrderASC,areaOrderDESC);
        return new PageInfo<>(list);
    }
3.多条件查询IN,foreach+可变长度参数<choose,when,otherwise>,传入list
    /**
     * 查询空闲的房子,根据buildingId查询,忽略单元
     * @param buildingIdList 根据buildingId查询房间,默认查询空间房间,如果status为false,
     *                   则也可以查询到非空闲的房间
     * @param status 默认为true,如果为false,则可以查询到非空闲房子
     * @return
     */
    List<House> queryHouseByBuildingId(
            @Param("buildingIdList") List<Integer> buildingIdList,
            @Param("status") Boolean... status);
    <select id="queryHouseByBuildingId" resultType="house">
<!--        SELECT * FROM c_house WHERE building_id IN (1,2,3) AND !status-->
        SELECT * FROM c_house
        WHERE
        building_id IN
        <foreach collection="buildingIdList" open="(" separator="," close=")" item="id">
            #{id}
        </foreach>
        <choose>
            <when test="status!=null and status.length>0 and !status[0]">
<!--                如果为false,则可以查询到非空闲房子-->
            </when>
            <otherwise>
                AND !status
            </otherwise>
        </choose>
    </select>
4.多条件查询进阶版<where,choose,when,otherwise,foreach,if>

    /**
     * 查询所有空闲的房子,显示到左边的框里面 TODO:后面再全查询的基础上进行
     */
    List<House> queryAllNoIntoHouse(
            @Param("buildingIdList") List<Integer> buildingIdList, // 楼栋
            @Param("storey") Integer storey, // 楼层
            @Param("areaMin") Double areaMin, // 面积
            @Param("areaMax") Double areaMax, // 面积
            @Param("areaOrderASC") Boolean areaOrderASC,
            @Param("areaOrderDESC") Boolean areaOrderDESC,// 排序
            @Param("status") Boolean... status // 如果为false,则可以查询到非空闲房子,其他情况 都是查询空闲房子
    );
    <select id="queryAllNoIntoHouse" resultType="house">
        SELECT
        c_house.id,
        c_owner_house.ownerId,
        c_building.num,c_building.floors,c_building.unit,
        c_house.storey,c_house.roomNum,c_house.area,c_house.into_date,c_house.status,c_house.building_id,
        user_owner.realname AS ownerName
        FROM c_house
        LEFT JOIN c_building ON c_building.id = c_house.building_id
        LEFT JOIN c_owner_house ON c_owner_house.houseId = c_house.id
        LEFT JOIN user_owner ON user_owner.id = c_owner_house.ownerId
        <where>
            <choose>
                <when test="buildingIdList!=null and buildingIdList.size()>0">
                    building_id IN
                    <foreach collection="buildingIdList" open="(" separator="," close=")" item="id">
                        #{id}
                    </foreach>
                </when>
                <otherwise>
<!--                    默认全查-->
                </otherwise>
            </choose>
            <if test="storey!=null">
                AND c_house.storey=#{storey}
            </if>
            <if test="areaMin!=null">
                AND c_house.area >= #{areaMin}
            </if>
            <if test="areaMax!=null">
                AND c_house.area <= #{areaMax}
            </if>
            <choose>
                <when test="status!=null and status.length>0 and !status[0]">
                    <!--                如果为false,则可以查询到非空闲房子-->
                </when>
                <otherwise>
                    AND !status
                </otherwise>
            </choose>
        </where>
        <if test="areaOrderASC">
            ORDER BY c_house.area
        </if>
        <if test="areaOrderDESC">
            ORDER BY c_house.area DESC
        </if>
    </select>
前后端交互controller层
// 2.处理搜索请求
    // 默认显示所有可以选择的房间,放到左侧的框里,如果选择,双击,这条数据跳到右边的框里
    @RequestMapping("/query/house")
    @ResponseBody
    public ResData queryHouse(
//            @RequestParam(value = "buildingList", defaultValue = "List[]") List<Building> buildingList, // 楼栋
//            Integer storey, // 楼层,没有用到
//            Double areaMin,
//            Double areaMax,
//            @RequestParam(value = "areaOrderASC",defaultValue = "false") Boolean areaOrderASC,
//            @RequestParam(value = "areaOrderDESC",defaultValue = "false") Boolean areaOrderDESC
            @RequestBody HouseFront houseFront
            ){
        Double areaMax = houseFront.getAreaMax();
        Double areaMin = houseFront.getAreaMin();
        Boolean areaOrderASC = houseFront.getAreaOrderASC();
        Boolean areaOrderDESC = houseFront.getAreaOrderDESC();
        Integer storey = houseFront.getStorey();
        List<Building> buildingList = houseFront.getBuildingList();
        List<Integer> buildingIds = buildingList.stream().map(Building::getId).collect(Collectors.toList());
        System.out.println("查询条件:"
                +areaMin+"/"
                +areaMax+"/"
                +buildingIds+"/"
                +storey+"/"
        );
        // 大小的问题
        if (!StringUtils.isBlank(areaMin) && !StringUtils.isBlank(areaMax)){
            if (areaMin.compareTo(areaMax)>0){
                return new ResData(1002, "最小面积不能大于最大面积", null);
            }
        }
        // 这个排序顺序不能两个都是true
        if (areaOrderASC && areaOrderDESC){
            return new ResData(1003, "排序条件冲突", null);
        }
        List<House> list = houseService.queryAllNoIntoHouse(buildingIds, storey, areaMin, areaMax, areaOrderASC, areaOrderDESC, true);
        list.forEach(System.out::println);
        return new ResData(200, "ok", list);
    }
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>新增业主</title>
    <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
    <script src="/js/jquery-3.5.1.js"></script>
    <script src="/bootstrap/js/bootstrap.js"></script>
    <script src="/js/axios.min.js"></script>
    <script src="/js/vue.min-v2.5.16.js"></script>
</head>
<body>
<div id="app">
    <br>
    录入业主基础信息:<br>
    用户名:   <input type="text" v-model="username">
    真实姓名:<input type="text" v-model="realname">
    电话号码:<input type="text" v-model="tel"><br>
    身份证号:<input type="text" v-model="identity">
    性别:
    <input type="radio" v-model="gender" value="男">男
    <input type="radio" v-model="gender" value="女">女
    <br>
    备注信息:<textarea v-model="notes" rows="3" cols="22"></textarea><br>
    <br>
    录入选房信息:<br>
    <div>
<!--        搜索所有可以选择的房子-->
        楼栋编号:
        <select v-model="buildingId">
            <option value="">--请选择楼栋--</option>
            <option v-for="building in buildingList" :value="building.id">{{building.num}}-{{building.unit}}</option>
        </select>
        <button @click="addBuildingId">添加楼栋</button>
        <button @click="resetSelectBuildsBtn">重选楼栋</button>
        <br>
        选中的楼栋为:
        <select multiple style="width: 100px" @dbclick="removeSelectedBuildingsBtn">
            <option v-for="building in selectedBuildingList" :value="building.id">{{building.num}}-{{building.unit}}</option>
        </select>
        楼层:
        <select v-model="selectFloor">
            <option value="">-选择楼层--</option>
            <option v-for="floors in floorList" :value="floors">{{floors}}层</option>
        </select>
        面积:
        <input type="text" v-model="areaMin" style="width: 50px" placeholder="小">---
        <input type="text" v-model="areaMax" style="width: 50px" placeholder="大">
        <button @click="areaASC">面积升序</button>
        <button @click="areaDESC">面积降序</button>
        <button @click="searchHouseBtn">搜索房子</button>
        <button @click="searchHouseBtnClr">重置搜索</button><br>
    </div>
<!--    TODO:可以选的房子的左侧的框-->
<!--    canSelectHouse-->
    可选的房子为:
    <select multiple style="width: 200px" @dblclick="selectHouseDbc">
        <option v-for="house in canSelectHouse" :value="house.id">
            {{house.num}}--{{house.unit}}--{{house.roomNum}}--{{house.area}}
        </option>
    </select>
    选中的房子为:
    <select  multiple style="width: 200px" @dblclick="removeSelectedHouseDbc">
        <option v-for="house in selectedHouse" :value="house.id">
            {{house.num}}--{{house.unit}}--{{house.roomNum}}--{{house.area}}平米
        </option>
    </select>
    <br>
    <button @click="add">添加</button>
    <button @click="reset">重置</button><br>
    <br>
</div>
<script>
    let app = new Vue({
        el:"#app",
        data:{
            // 首先是用户的基础信息
            username:"",
            realname:"",
            tel:"",
            gender:"男",
            identity:"",
            buildingId:"",
            houseId:"",
            notes:"",
            // 然后是查询的数据
            // 然后是选中的楼栋的id,1栋1单元...
            floorList : Array.from({ length: 12 }, (v, i) => i + 1), // 最大有12层楼,因此这里可以选择1到12
            selectedBuildingList:[], // List<Building> buildingList, // 楼栋
            selectFloor:"", // 楼层
            areaMin:"", // 面积
            areaMax:"", // 面积
            // 和排序相关的
            areaOrderASC:false,
            areaOrderDESC:false,
            // 在左侧边框里面显示可以选择的所有房间信息
            canSelectHouse:[],
            // 在右侧框里面显示已经选择的所有房间信息
            selectedHouse:[],
            buildingList:[]
        },
        methods:{
            searchHouseBtnClr(){
                // 重置搜索
                this.selectedBuildingList =[]
                this.buildingId = ""
                this.selectFloor="" // 楼层
                this.areaMin="" // 面积
                this.areaMax="" // 面积
                    // 和排序相关的
                this.areaOrderASC=false
                this.areaOrderDESC=false
                this.queryHouse();
            },
            // 如果选中了一个楼栋,就添加一个楼栋来,如果有重复的就不添加
            addBuildingId(){
                let selected = this.buildingList.find(b=>b.id===this.buildingId)
                // 这个selected是一个 building对象
                console.log(selected)
                // 只有selected里面有值,并且没有重复的时候才能添加进来
                if (selected && !this.selectedBuildingList.find(b=>b.id===selected.id)){
                    this.selectedBuildingList.push(selected)
                }
            },
            // 如果双击选中的楼栋,则删除已经选中的楼栋
            removeSelectedBuildingsBtn(event){
                const optionValue = event.target.value; // 获取双击的对象的值
                this.selectedBuildingList = this.selectedBuildingList.filter(building=>building.id != optionValue)
            },
            // 在可选的房子的框里面双击,则在选中的房子的框里面加这个房子,并且在可选的房子的框里面删除被选的房子
            selectHouseDbc(event){
                const optionValue = event.target.value; // 获取双击的对象的值
                // TODO:注意两点,这个过滤后的还是一个集合,需要获取第一个元素,另外要用== 不要用===
                const house = this.canSelectHouse.filter(house=>house.id == optionValue)[0]
                console.log(house)
                this.selectedHouse.push(house)
                this.canSelectHouse = this.canSelectHouse.filter(house=>house.id != optionValue)
                console.log(this.canSelectHouse)
                console.log(this.selectedHouse)
            },
            removeSelectedHouseDbc(event){
                const optionValue = event.target.value; // 获取双击的对象house
                console.log(optionValue)
                const house = this.selectedHouse.filter(house=>house.id == optionValue)[0]
                this.selectedHouse = this.selectedHouse.filter(house=>house.id !=optionValue)
                this.canSelectHouse.push(house)
            },
            //如果选中了一个房子,就添加一个进来,如果有重复的就不添加
            addHouseId(){
                const selectedOption = this.allhouseList.find(house=>house.id===this.houseId);
                if (selectedOption && !this.houseList.find(house => house.id === selectedOption.id)) {
                    this.houseList.push(selectedOption);
                }
            },
            removeSelectedOption(event) {
                const optionValue = event.target.value;//获取双击的对象的值
                console.log(optionValue);
                this.houseList = this.houseList.filter(house => house.id != optionValue);
            },
            add(){
                let param = {};
                param.username = this.username;
                param.realname = this.realname;
                param.tel = this.tel;
                param.gender = this.gender;
                param.identity = this.identity;
                param.notes = this.notes;
                param.houseList = this.selectedHouse;
                axios.post("/owner/add",param)
                    .then(response=>{
                        if(response.data.code==200)
                        {
                            location.href="/user/ownerPage"
                        }
                        else
                        {
                            alert(response.data.msg);
                        }
                    })
            },
            // 重新输入用户信息
            reset(){
                this.username=""
                this.realname=""
                this.tel=""
                this.identity=""
                this.notes=""
            },
            // 重新选择楼栋
            resetSelectBuildsBtn(){
                this.selectedBuildingList =[]
                this.buildingId = ""
            },
            areaASC(){
                this.areaOrderASC=true;
                this.areaOrderDESC=false;
                this.queryHouse();
            },
            areaDESC(){
                this.areaOrderASC=false;
                this.areaOrderDESC=true;
                this.queryHouse();
            },
            // 查询所有可以选择的房间
            queryHouse(){
                let jsonObj = {}
                jsonObj.buildingList=this.selectedBuildingList;
                jsonObj.storey=this.selectFloor;
                jsonObj.areaMin=this.areaMin;
                jsonObj.areaMax=this.areaMax;
                jsonObj.areaOrderASC=this.areaOrderASC;
                jsonObj.areaOrderDESC=this.areaOrderDESC;
                axios.post("/owner/query/house",jsonObj)
                    .then(response=>{
                        if (response.data.code==200){
                            this.canSelectHouse = response.data.data;
                        }else {
                            alert(response.data.msg)
                        }
                    })
            },
            searchHouseBtn(){
                this.queryHouse();
            },
        },
        mounted(){
            // 首先要获得所有可以选择的房子
            this.queryHouse();
            //查询楼栋下拉列表
            axios.get("/build/list/all")
                .then(response=>{
                    this.buildingList = response.data.data;
                })
        }
    });
</script>
</body>
</html>
专门用来前后端交互的实体类
package com.tianju.entity;
import com.alibaba.druid.filter.AutoLoad;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
 * 专门用来和前端交互的一个对象;
 * 用于选房页面的复杂查询
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HouseFront {
    private List<Building> buildingList;
    private Integer storey;
    private Double areaMin;
    private Double areaMax;
    private Boolean areaOrderASC;
    private Boolean areaOrderDESC;
}
5.传入Boolean类型的可变长度参数
    /**
     * 超级管理员能看离职员工和在职员工的信息,
     * 其他员工只能看在职员工信息。
     * @param su 表示是否是超级管理员,su 为true 时,为超级管理员
     * @return
     */
    List<User> queryAllManagerUser(
            @Param("su") Boolean... su
    );
<!--    修改用户信息,根据用户id查询一条出来-->
    <select id="queryManagerById" resultType="user">
<!--        SELECT * FROM user_manager WHERE id = #{id}-->
        SELECT * FROM
        (SELECT
        user_manager.id,
        user_role.id AS userRoleId,user_role.role_id AS typeId,user_role.user_id AS userId,
        user_manager.username,user_manager.password,user_manager.tel,
        user_manager.notes AS userNotes,user_manager.create_time,user_manager.update_time,
        user_manager.operator,user_manager.status,user_manager.firstLog,
        user_role_type.name AS typeName,user_role_type.notes AS typeNotes
        FROM user_manager
        LEFT JOIN user_role ON user_manager.id = user_role.user_id
        LEFT JOIN user_role_type ON user_role.role_id = user_role_type.id
        ) t
        WHERE t.id = #{id}
        <!--        一般人员只能查询在职员工,超级管理员可以查所有员工-->
        <choose>
            <when test="su==null or su.length < 1">
                AND firstLog !='100' AND status
            </when>
        </choose>
    </select>

6.多个条件排序 ORDER BY num,unit

controller层
    @RequestMapping("/list")
    @ResponseBody
    public ResData buildingList(
            @RequestParam(value = "pageNum",defaultValue = "1")Integer pageNum,
            @RequestParam(value = "pageSize",defaultValue = "10")Integer pageSize,
            @RequestParam(value = "order",defaultValue = "false")Boolean order){
        PageInfo<Building> pageInfo = buildingService.queryAll(pageSize, pageNum,order);
        System.out.println(pageInfo);
        return new ResData(200,"ok",pageInfo);
    }
service层
    @Override
    public PageInfo<Building> queryAll(Integer pageSize,Integer pageNum,Boolean order) {
        PageHelper.startPage(pageNum, pageSize);
        List<Building> list = buildingMapper.queryAll(order);
        return new PageInfo<>(list);
    }
mapper层
List<Building> queryAll(Boolean order);
xml文件的sql
    <select id="queryAll" resultType="building">
        SELECT * FROM c_building
        <if test="order">
            ORDER BY num,unit
        </if>
    </select>
7.一条新增影响多个数据库的表@Transactional

controller层
// 3.处理新增用户信息
    Boolean addFlag = true;
    @RequestMapping("/add")
    @ResponseBody
    public ResData addOwner(
            @RequestBody Owner owner
    ){
        System.out.println(owner);
        // 1.判断前端输入的数据全不
        // 2.如果确认不新添房子,则只修改user_owner表,则该用户不能登陆账号
        // 3.如果新增了房子,则修改user_owner表,c_house表,c_owner_house表
        if (StringUtils.isBlank(owner.getUsername(),owner.getRealname(),owner.getIdentity(),owner.getGender())){
            return new ResData(1001, "必填项为空", null);
        }
        if (!StringUtils.isLegalPhone(owner.getTel())){
            return new ResData(1002, "手机号码不合法", null);
        }
        // TODO:这样可以吗???
        if (addFlag){
            if (owner.getHouseList().size()<1){
                addFlag = false;
                return new ResData(2001, "确定新增一条未选择房间的业主?", null);
            }
        }
        // 初始化一下密码,手机号前4位
        owner.setPassword(owner.getTel().substring(0,4));
        // 获取session
        User user = (User) session.getAttribute("user");
        if (user!=null){
            owner.setOperator(user.getOperator());
        }
        Integer flag = ownerService.add(owner);
        if (flag<1){
            return new ResData(3001, "系统繁忙,请稍后重试", null);
        }
        addFlag = true; // 新增成功再置为true,下一条就会再次判断是不是没填
        return new ResData(200,"新增用户信息成功",null);
    }
service层@Transactional
    @Transactional
	@Override
    public Integer add(Owner owner) {
        // 如果新用户只是意向客户
        if (owner.getHouseList().size()<1){
            owner.setHouseNum(0);
            return ownerMapper.add(owner);
        }
        // 买房的客户变成了业主
        Integer num = 0; //影响数据库的数据的行数
        ownerMapper.add(owner);
        for (House house:owner.getHouseList()){
            // 房间状态改为入住的状态
            houseMapper.intoHouse(house.getId());
            // 房屋-业主对应表新增一条数据
            houseMapper.addOwnerHouse(owner.getId(), house.getId());
            num +=2;
        }
        return num;
    }
    <update id="intoHouse">
        UPDATE c_house SET into_date = NOW(),status=1 WHERE id=#{id}
    </update>
    <insert id="addOwnerHouse">
        INSERT INTO c_owner_house(ownerId,houseId) VALUES (#{ownerId},#{houseId})
    </insert>
mybatis使用报错集锦
1.忘记加 resultType=“opus”
报错信息:
Error querying database. Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement ‘com.tianju.dao.OpusMapper.queryByAuthorIdAndLikeName’. It’s likely that neither a Result Type nor a Result Map was specified.
报错原因:
查询返回值类型没写

解决方案:
加上resultType=“opus”,如下:
<!--    多种条件查询-->
    <select id="queryByAuthorIdAndLikeName" resultType="opus">
        SELECT t_opus.*,tt.name AS typename,tu.username
        FROM t_opus
        LEFT JOIN t_types tt ON t_opus.typeId = tt.id
        LEFT JOIN user_tab tu ON t_opus.authorId = tu.id
        <where>
            <if test="authorId!=null">
                authorId = #{authorId}
            </if>
            <if test="name!=null and name!=''">
                AND t_opus.name LIKE #{name}
            </if>
        </where>
        ORDER BY t_opus.id DESC
    </select>
2.前端显示list页面数据为空
问题描述:
pageHelper里面的没有数据,前端显示为空:
PageInfo{pageNum=0, pageSize=0, size=0, startRow=0, endRow=0, total=0, pages=0, list=null, firstPage=0, prePage=0, nextPage=0, lastPage=0, isFirstPage=false, isLastPage=false, hasPreviousPage=false, hasNextPage=false, navigatePages=0, navigatepageNums=null}
原因分析:
pageHelper有顺序要求

解决方案:
 
    /**
     * 分页在service层完成,利用pageHelper 在service层做完分页的事情
     */
    @Override
    public PageInfo<Opus> queryByAuthorIdAndLikeName(String name, Integer authorId, Integer pageNum,Integer pageSize) {
        // 先要用pageHelper的 分页,然后执行查询
        PageHelper.startPage(pageNum, pageSize);
        List<Opus> list = opusMapper.queryByAuthorIdAndLikeName(name, authorId);
        PageInfo<Opus> pageInfo = new PageInfo<>(list);
        return pageInfo;
    }
3.由于mybatis版本引起的问题
报错信息:
org.apache.ibatis.exceptions.PersistenceException:
Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘username’ in ‘class java.lang.String’
Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named ‘username’ in ‘class java.lang.String’
报错原因
如果3.4.5版本,3.4开头的就是低版本,并且username要在动态sql中使用,即,if,where,中使用,则一个参数也需要加@param,如果是3.5.9以上版本,则可以不加

解决方案:
1.UserMapper里面加@param
// 如果3.4.5版本,3.4开头的就是低版本,并且username要在动态sql中使用
    // 即,if,where,中使用,则一个参数也需要加@param,如果是3.5.9以上版本,则可以不加
    List<User> findByUsernameSQL(@Param("username") String username);
2.mybatis版本换成3.5以上
<!--   mybatis 框架-->
<!--        小版本会导致userMapper findByUsernameSQL-->
<!--        3.5.9-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>
在项目中的案例:

接口java文件
List<Company> queryByLikeName(@Param("name") String name);
xml文件
    <select id="queryByLikeName" resultType="company">
        SELECT com_company.*,com_type.name AS typename,com_user.username AS username FROM com_company
        LEFT JOIN com_type ON com_company.type_id=com_type.id
        LEFT JOIN com_user ON com_company.user_id=com_user.id
        <where>
            <if test="name!=null and name !=''">
                com_company.name LIKE #{name}
            </if>
        </where>
    </select>
可以在这里看相关的包
https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api

4. but found: 11:返回值类型错误
报错信息:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 11
报错原因:
类型错误,如果查询出的结果可能是多个,则需要用List进行接收

解决方案:
改成list
List<User> findByUsernameSQL(@Param("username") String username);
5.Mapper.XML文件中的id重复
报错信息
15-Jun-2023 10:59:46.335 信息 [http-nio-8080-exec-7] org.apache.catalina.core.ApplicationContext.log 将servlet[com.tianju.servlet.user.LoginServiceVue]标记为不可用
 15-Jun-2023 10:59:46.335 严重 [http-nio-8080-exec-7] org.apache.catalina.core.StandardWrapperValve.invoke 分配异常的servlet [com.tianju.servlet.user.LoginServiceVue]
 java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.tianju.dao.OpusMapper.removeById
报错原因:
mapper中存在id重复的值

解决方案:
修改其中一个mapper的id
6.删除不生效:Mapper.xml文件中的<delete写成了select
问题描述:
在前端点击删除按钮,刷新前端后该数据依然存在,数据库中的数据确实被删除;如果项目重启,前端才没有该条数据,开始以为数缓存的问题;
这个问题比较奇怪,尝试了清除mybatis的缓存等方法都没有解决,

错误原因:
这里的delete,写成了select

解决方案:改成delete
<!--    TODO:如果这里的delete,写成了select-->
    <delete id="removeById" parameterType="integer">
        DELETE FROM t_opus WHERE id = #{id}
    </delete>
7.修改数据:报500异常,update 的逗号,没加
报错信息:
Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ‘intro=‘123’,
 updateTime=now() WHERE id=47’ at line 11
SQL: UPDATE t_opus SET name=?, typeId=? intro=?, updateTime=now() WHERE id=?
 Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'intro=‘123’,
updateTime=now() WHERE id=47’ at line 11
排查bug的顺序:
(1)先看前端,报500异常,说明java代码有问题;

(2)在后台的tomcat localhost log里面定位错误是SQL语句的问题;

(3)在service这里可以看到执行的sql,如下
UPDATE t_opus SET name=?, typeId=? intro=?, updateTime=now() WHERE id=? 

(4)最终的原因是OpusMapper.xml里面的动态sql写错了;

解决方法:加上逗号
OpusMapper.java接口
    /**
     * 修改一条图书信息
     */
    Integer updateById(@Param("opus") Opus opus);
OpusMapper.xml中对应的动态sql
<!--    如果@Param("opus") 和上面对比-->
    <update id="updateById">
        UPDATE t_opus SET
        <if test="opus.name!=null and opus.name!=''">
            name=#{opus.name},
        </if>
        <!--            如果的Integer类型的id,加上了不为''的条件-->
        <if test="opus.typeId!=null">
<!--            如果这里没有加 逗号 -->
            typeId=#{opus.typeId},
        </if>
        <if test="opus.intro!=null and opus.intro!=''">
            intro=#{opus.intro},
        </if>
        updateTime=now() WHERE id=#{opus.id}
    </update>
8.xml文件和java文件名不一致:
报错信息:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.tianju.dao.CompanyMapper.queryByLikeName


原因解析:
后台显示sql并没有执行,再结合 Invalid bound statement (not found):的报错信息,检查文件名确实不一致


9.在xml文件中的resultType写错映射对象
报错信息:
org.apache.catalina.core.StandardWrapperValve.invoke 分配异常的servlet [com.tianju.servlet.company.CompanyListServlet]
 java.lang.ClassNotFoundException: Cannot find class: user

10.日期类型判断时加了!=''引发的异常
报错信息:
Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String

<!--            TODO:数字和日期类型不要加 createTime!=''-->
            <if test="createTime!=null and createTime!=''">
                create_time=#{createTime},
            </if>
解决方案:
<if test="createTime!=null">
	create_time=#{createTime},
</if>
总结
1.mybatis可以实现SQL语句和Java代码的解耦;
 2.mybatis使用,tomcat的war包,和spring的jar包,分页的拦截器;
 3.mybatis项目应用初步,SqlSessionUtils工具类,泛型的方式;
 4.多条件查询<where,choose,when,otherwise,foreach,if>,排序order;
 5.事务回滚,要么都成功,要么都失败,@Transactional注解;
 6.在tomcat和spring框架下,常见的报错集锦



















