华清远见重庆中心-框架阶段技术总结

框架

一套规范。

实际是他人实现的一系列接口和类的集合。通入导入对应框架的jar文件(maven项目导入对应的依赖),进行适当的配置,就能使用其中的所有内容。

开发者可以省去很多模板代码,如dao中的CRUD,MVC模式下层与层之间的关联。只需要集中精力实现项目中的业务逻辑部分。

Java主流框架

Spring、SpringMVC、MyBatis、MyBatisPlus、Hibernate、JPA等。

SSH:最初是Spring+Stucts2+Hibernate组成,之后Stucts2被SpringMVC取代。

SSM:Spring+SpringMVC+MyBatis

新项目使用SpringBoot,早起的SSH项目由于维护成本高,基本不会推翻重做,但会维护一些SSM项目。

无论是SSH还是SSM,Spring、SpringMVC必不可少。从2004年推出至今,依旧是主流框架中不可获取的一部分。

Spring

概念

一个轻量级开源的Java框架。是一个管理项目中对象的容器,同时也是其他框架的粘合器,目的就是对项目进行解耦。

轻量级:对原有代码的侵入很小。

Spring的核心是IOC控制反转和AOP面向切面编程

组成

名词解释

IOC

Inversion Of Control 控制反转

DI

Dependency Injection 依赖注入

控制反转(IOC)是一种思想,就是让创建对象的控制权由自身交给第三方,控制反转这种思想,通过依赖注入(DI)的方式实现。

IOC和DI其实都是在描述控制反转,IOC是思想,DI是具体实现方式。

这里的第三方,就是Spring。Spring是一个容器,可以管理所有对象的创建和他们之间的依赖关系。

可以理解为:"Spring就是用来管理对象的,在需要用到某个对象的时候,帮我们自动创建"。

如Servlet+JSP模式写Web项目时,会在控制层Servlet中创建业务逻辑层Service对象,在Service层中创建数据访问层Dao对象。

有了Spring后,就不会出现new这些对象的代码了。

Spring需要导入对应的jar文件后,定义一个配置文件,在该配置文件中配置程序运行过程中所需的对象。

AOP

Aspect Orintend Programming 面向切面编程

bean标签常用属性

属性作用
class定义类的全限定名
id定义对象的名称
lazy-init是否为懒加载。默认值为false,在解析配置文件时就会创建对象。设置为true表示懒加载,只有在getBean()时才会创建对象。
scope单例/原型模式。默认值为singleton,表示单例模式,只会创建一个对象。设置为prototype,表示原型模式,每调getBean()就创建一个对象。
init-method初始化时触发的方法。在创建完该对象时自动调用的方法。该方法只能是无参方法,该属性的值只需要写方法名即可
destory-method销毁时触发的方法。Spring容器关闭时自动调用的方法,该方法只能是无参方法。只有在单例模式下有效。

属性注入

给某个bean添加属性的方式有两种:构造器注入和setter注入

setter注入

这种方式注入属性时,类中必须要有set方法

在bean标签中,加入<property></property>标签,

该标签的name属性通常表示该对象的某个属性名,但实际是setXXX()方法中的XXX单词。

如有age属性,但get方法为getNianLing(),name属性就需要写成nianLing。

该标签的value属性表示给该类中的某个属性赋值,该属性的类型为原始类型或String

该标签的ref属性表示给该类中除String以外的引用类型属性赋值,值为Spring容器中另一个bean的id。

如果使用了注解,就无需在类中添加setter方法,spring配置文件中也无需设置autowire。

Spring核心注解

在Spring配置文件中加入

<!--设置要扫描的包,扫描这个包下所有使用了注解的类-->
<context:component-scan base-package="com.hqyj.spring02.bookSystem"></context:component-scan>

类上加的注解

  • @Component
    • 当一个类不好归纳时,定义为普通组件
  • @Controller
    • 定义一个类为控制层组件
  • @Service
    • 定义一个类为业务层组件
  • @Repository
    • 定义一个类为持久层(数组访问层)组件
  • @Lazy/@Lazy(value=true)
    • 设置该类为懒加载。
  • @Scope(value="singleton/prototype")
    • 设置为单例/原型模式。

说明

以上注解公共特点

  • 都是将对应类的对象注入到Spring容器中,用于替换配置文件中的bean标签
  • 都默认是单例模式非懒加载
  • 默认注入的对象id为当前类的类名首字母小写形式
    • 如在BookDao类上添加,id默认为bookDao
  • 可以通过注解的value属性自定义注入的对象的id名,如@Component(value="key")表示注入的对象id为key

属性上加的注解

  • @Autowired

    • 优先使用byType方式从Spring容器中获取对应类型的对象自动装配。先检索Spring容器中对应类型对象的数量,如果数量为0直接报错;数量为1直接装配

      数量大于1,会再尝试使用byName方式获取对应id的对象,但要配合@Qualifier(value="某个对象的id")一起使用,指定id进行装配

  • @Qualifier(value="某个对象的id")

    • 配合@Autowired注解,使用byName方式获取某个对象id的bean进行装配
  • @Resource(name="某个对象的id")

    • 该注解相当于@Autowired+@Qualifier(value="某个对象的id")

    • 优先使用byName方式,从Spring容器中检索name为指定名的对象进行装配,如果没有则尝试使用byType方式,要求对象有且只有一个,否则也会报错。

说明

  • 如果要在某个类中使用Spring容器中的某个对象时,只需定义成员变量,无需创建对象,通过@Autowired或@Resource注解进行自动装配

  • 实际开发中,绝大部分情况下,需要自动装配对象有且只有一个,并且命名规范,所以@Autowired或@Resource区别不是很大。@Autowired优先使用byType方式,@Resource优先使用byName方式

  • 如果@Resource不能使用,是因为缺少javax.annotation包,需要引入对应依赖

    <dependency>
        <groupId>javax.annotation</groupId>
        <artifactId>javax.annotation-api</artifactId>
        <version>1.3.2</version>
    </dependency>

如何初始化Spring容器(解析Spring配置文件)

在控制台应用程序中,可以在main方法中通过ClassPathXmlApplicationContext来解析Spring配置文件,初始化Spring容器。

在web项目中没有main方法,只有servlet中的service方法,如果在service方法中创建ClassPathXmlApplicationContext对象,会每次访问都执行。

而Spring容器只需初始化一次,在项目启动时就解析Spring配置文件,全局监听器就是一个很好的选择。

spring-web包中提供了一个ContextLoaderListener类,它实现了ServletContextListener,属于项目级别的全局监听器。

这个类需要一个contextConfigLocation参数,表示要解析的Spring配置文件的路径。

这个监听器会在项目启动时,读取指定的Spring配置文件路径,并且创建WebApplicationContext对象,即Spring容器。

JDBCTemplate常用方法

方法作用说明
query(String sql,RowMapper mapper)无条件查询返回值为List集合
update(String sql)无条件更新(删除、修改)返回值为受影响的行数
query(String sql,RowMapper mapper,Object... objs)条件查询可变参数为?的值
update(String sql,Object... objs)条件更新(增加、删除、修改)可变参数为?的值
queryForObject(String sql,RowMapper mapper)无条件查询单个对象返回值为指定对象
queryForObject(String sql,RowMapper mapper,Object... objs)条件查询单个对象返回值为指定对象
execute(String sql)执行指定的sql无返回值

AOP

概念

Process Oriented Programming 面向过程编程POP

Object Oriented Programming 面向对象编程OOP

Aspect Oriented Programming 面向切面编程AOP

以上都是编程思想,但AOP不是OOP和POP的替代,而是增强、拓展和延伸。主流编程思想依然是OOP。

作用

在传统的OOP思想中,我们将程序分解为不同层次的对象,通过封装、继承、多态等特性,

将对象组织成一个整体来完成功能。但在某些场景下,OOP会暴露出一些问题。

如在处理业务中,除了核心的业务代码外,通常还会添加一些如果参数验证、异常处理、事务、记录日志等操作。

这些内容会分散在各个业务逻辑中,依旧会出现大量重复操作。如果将这些重复的代码提取出来,在程序编译运行时,

再将提出来的内容应用到需要执行的地方,就可以减少很多代码量。方便统一管理,更专注于核心业务。

简单来说,就是将不同位置中重复出现的一些事情拦截到一处进行统一处理。

ORM

Object Relational Mapping 对象关系映射

创建Java类的对象,将其属性和数据库表中的字段名之间构建映射关系。

这样通过操作该类创建的一个对象,就能操作数据库中对应的数据表。

ORM框架就是对象关系映射框架,用于简化JDBC。

主流的ORM框架有Hibernate、JPA、MyBatis、MyBatisPlus

MyBatis

一个半自动化的ORM框架。原本叫做ibatis,后来升级改名为MyBatis。

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

使用XML文件或注解的形式,将SQL语句映射为持久层(dao层)中的方法。

官网https://mybatis.org/mybatis-3/zh/index.html

特点

 

SSM项目搭建

整体流程

1.创建基于Maven的webapp项目

2.修改web.xml版本为4.0,创建java、resoureces目录、项目包结构、web-inf下的页面目录

3.导入依赖

spring-webmvc

mybatis

mybatis-spring

mysqldruid

spring-jdbc

jstl

4.配置Spring

创建application.xml

扫描项目根包

配置web.xml

设置全局参数: ,读取application.xml文件

设置全局监听器:ContextLoaderListener,初始化Spring容器

5.配置SpringMVC

创建springmvc.xml

扫描控制层所在包

设置内部资源视图解析器:InternalResourceViewResolver,设置前后缀

配置web.xml

设置请求分发器:DispatcherServlet,在其中读取springmvc.xml配置文件

过滤器解决请求中文乱码:CharacterEncodingFilter

6.配置MyBatis

创建mybatis-config.xml(官网模板)

在application.xml中注入

数据库连接池Druid:DruidDataSource

SQL会话工厂:SqlSessionFactoryBean

映射扫描配置器:MapperScannerConfigurer

7.执行sql语句

创建dao层接口,定义方法

在resoureces下创建mapper目录创建sql映射文件xx.xml(官网模板),在其中定义sql语句

查询<select></select>

单表查询

dao中的方法

List<BookType> queryAll();

sql映射文件

  • 当数据库表中的字段名和实体属性一致或开启了驼峰命名映射,使用resultType

    <select id="queryAll" resultType="com.xxx.entity.BookType">
        select * from book_type
    </select>
  • 当数据库表中的字段名和实体属性不一致,使用resultMap

    <select id="queryAll" resultMap="map">
        select * from book_type
    </select>
    <!--自定义结果集映射-->
    <resultMap id="map" type="com.xxx.entity.BookType">
        <!--主键使用id标签,其他字段使用result标签-->
        <!--column对应字段名 property对应属性名-->
        <id column="type_id" property="typeId"></id>
        <result column="type_name" property="typeName"></result>
    </resultMap>

多表查询

多对一查询

多对一表示多个从表实体对应一个主表实体。如多本图书对应一个图书类型。

在从表实体中,额外添加一个属性:对应的主表实体对象

public class BookInfo {
    private int bookId;
    private int typeId;
    private String bookName;
    private String bookAuthor;
    private int bookPrice;
    private int bookNum;
    private String publisherDate;
    private String bookImg;
    //多对一查询,额外添加外键对应的实体对象
    private BookType bt;
}

dao中的方法

List<BookInfo> queryAll();

sql映射文件

  • 关联查询:构建特殊的sql语句

    • 适合外键对应的表中字段比较少的情况下使用
    <!--sql语句除了查询自身表之外,还要查询关联的表中的字段,将其命名为"实体对象.属性"-->
    <select id="queryAll" resultType="com.xxx.entity.BookInfo">
        select bi.*,type_name as 'bt.typeName' from book_info bi,book_type bt where bi.type_id=bt.type_id
    </select>
  • 连接查询(不建议)

    <select id="queryAll" resultMap="booksMap">
        select *
        from book_info bi,
        book_type bt
        where bi.type_id = bt.type_id
    </select>
    <resultMap id="booksMap" type="com.hqyj.ssm02.entity.BookInfo">
        <!--主键用id标签,其他字段用result标签赋值-->
        <id property="bookId" column="book_id"></id>
        <result property="bookName" column="book_name"></result>
        <result property="typeId" column="type_id"></result>
        <result property="bookAuthor" column="book_author"></result>
        <result property="bookPrice" column="book_price"></result>
        <result property="bookNum" column="book_num"></result>
        <result property="bookImg" column="book_img"></result>
        <result property="publisherDate" column="publisher_date"></result>
        <!--外键对应的实体对象使用association标签,通过property属性对应实体对象名-->
        <association property="bt" >
            <!--给外键对象属性赋值-->
            <id column="type_id" property="typeId"></id>
            <result column="type_name" property="typeName"></result>
        </association>
    </resultMap>
  • 子查询

    • 适合外键对应的表中字段比较多的情况
    <!--1.查询从表-->
    <select id="queryAll" resultMap="booksMap">
        select * from book_info
    </select>
    <!--2.自定义结果集映射,使用type_id进行子查询,下方的sql-->
    <resultMap id="booksMap" type="com.hqyj.ssm02.entity.BookInfo">
        <!--由于使用type_id进行了子查询,所以如果要给type_id赋值,需要再次进行映射-->
        <result column="type_id" property="typeId"></result>
        <!--外键对应的实体对象,使用association赋值-->
        <!--如果这里的子查询来自当前映射文件-->
        <association property="bt" column="type_id" select="findTypeByTypeId"></association>
        <!--如果这里的子查询来自于其他dao中的方法-->
        <!--<association property="bookType" column="type_id" select="com.hqyj.ssm02.dao.BookTypeDao.findById"></association>-->
    </resultMap>
    
    <!--3.根据type_id查询完整对象-->
    <select id="findTypeByTypeId" resultType="com.hqyj.ssm02.entity.BookType">
        select * from book_type where type_id=#{typeId}
    </select>

一对多查询

一对多表示一个主表实体对应多个从表实体。如一个图书类型对应多本图书。

在主表对应的实体类中,额外添加一个属性:多个从表对象的集合

public class BookType {
    private int typeId;
    private String typeName;
    private List<BookInfo> books;
}

dao中的方法

BookType queryBooksByType(int typeId);

sql映射文件

  • 连接查询

    <!--一对多查询,方式一:连接查询-->
    <select id="queryBooksByType" resultMap="testMap">
        select *
        from book_info bi
        inner join book_type bt on bi.type_id = bt.type_id
        where bi.type_id = #{typeId}
    </select>
    <!--自定义结果集映射-->
    <resultMap id="testMap" type="com.hqyj.ssm02.entity.BookType">
        <id property="typeId" column="type_id"></id>
        <result property="typeName" column="type_name"></result>
        <!--集合类型的属性,使用collection标签,使用ofType设置集合中的对象类型-->
        <collection property="books" ofType="com.hqyj.ssm02.entity.BookInfo">
            <id column="book_id" property="bookId"></id>
            <result column="book_name" property="bookName"></result>
            <result column="type_id" property="typeId"></result>
            <result column="book_author" property="bookAuthor"></result>
            <result column="book_num" property="bookNum"></result>
            <result column="book_price" property="bookPrice"></result>
            <result column="book_img" property="bookImg"></result>
            <result column="publisher_date" property="publisherDate"></result>
        </collection>
    </resultMap>
  • 子查询

    <!--一对多查询,方式二:子查询-->
    <!--1.根据类型编号查询自身表-->
    <select id="queryBooksByType" resultMap="testMap">
        select *
        from book_type
        where type_id = #{typeId}
    </select>
    <!--2.设置结果集映射-->
    <resultMap id="testMap" type="com.hqyj.ssm02.entity.BookType">
        <id column="type_id" property="typeId"></id>
        <result column="type_name" property="typeName"></result>
        <!--集合对象,使用collection标签。使用type_id字段执行子查询getBooksByTypeId,将结果映射到books属性-->
        <collection property="books" column="type_id" select="getBooksByTypeId"></collection>
    </resultMap>
    <!--3.子查询,根据类型编号查询所有对应的图书集合-->
    <select id="getBooksByTypeId" resultType="com.hqyj.ssm02.entity.BookInfo">
        select *
        from book_info
        where type_id = #{typeId}
    </select>

总结

  • 多对一

    如果查询要以从表数据为主,关联主表相应的数据时,属于多对一查询。如查询所有图书的同时,显示其类型。

    建议使用子查询或自定义特殊的sql语句。都需要在从表实体中添加一个主表对象属性。

    • 主表字段比较少,建议使用自定义特殊的sql语句,保证字段重命名为"主表对象.属性"

    • 主表字段比较多,建议使用子查询,使用<association>标签映射主表对象属性

  • 一对多

    如果查询要以主表数据为主,关联该主键对应的从表实体对象的集合时,属于一对多查询。如查询某个类型,同时显示该类型下的所有图书。

    建议使用子查询。在主表实体中添加一个从表集合属性。

    使用<collection>标签映射从表集合属性

多条件查询

参考#{}的用法

#{}和${}

在mybatis的sql映射文件中,可以使用#{}和${}表示sql语句中的参数。

#{}相当于预处理,会将参数用?占位后传值

${}相当于拼接,会将参数原样拼接

通常使用#{},防止sql注入

-- #{}会自动用''将参数引起来,如参数为admin
select * from user where username=#{username}
select * from user where username='admin'
-- ${}会将参数原样拼接,如参数为admin
select * from user where username=${username}
select * from user where username=admin

#{}的使用

  • 如果dao层接口中的方法参数为一个原始类型或字符串时

    • sql语句#{}中的参数是接口中方法的形参名
    public interface UserDao{
        User findById(int userId);
    }
    <select id="findById" resultType="com.xxx.entity.User">
        select * from user where id=#{userId}
    </select>
  • 如果dao层接口中的方法参数为一个实体对象时

    • sql语句#{}中的参数必须是接口中方法参数的某个属性
    public interface UserDao{
        User login(User user);
    }
    <select id="login" resultType="com.xxx.entity.User">
        select * from user where username=#{username} and password=#{password}
    </select>
  • 如果dao层接口中的方法参数不止一个时

    • 给每个参数添加@Param("自定义参数名")

    • sql语句#{}中的参数必须是@Param注解中自定义的名称

public interface UserDao{
    User update(@Param("bianhao")int id,@Param("mima")String password);
}
<update id="update">
    update user set password=#{mima} where id=#{bianhao}
</update>

${}的使用

当需要拼接的参数不能带引号时,必须使用${},如动态表名、排序条件等

动态表名

select * from ${表名}

排序条件

select * from 表名 order by ${字段}

删除<delete></delete>

删除单个

delete from 表 where 字段 = 值

只需一个值即可,通常为主键id

dao层接口

int delete(int id);

sql映射文件

<delete id="delete">
    delete from book_info where book_id=#{id}
</delete>

删除多个

delete from 表 where 字段 in(数据集合)

如delete from book_info where book_id in (1001,1002,1005)

in后面的内容通常是一个数组

前端页面通过复选框选中要删除的数据,获取选中的数据的id,保存到一个数组中

dao层接口

int deleteByIds(@Param("idList")List<Integer> idList)

sql映射文件

<delete id="deleteByIds">
    delete from book_info where book_id in 
    <!-- 
         foreach标签用户遍历dao层传递的集合对象
        collection表示要遍历的集合
        open和close表示将遍历出的数据使用什么开头和结尾
        separator表示将遍历出的数据用什么分隔
    -->
    <foreach collection="idList" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

foreach标签可以用于sql语句中条件是集合的情况,配合where条件使用,dao层接口中方法参数为集合。

如where 字段 in/not in (值1,值2,值3)

添加<insert></insert>

添加时的参数虽多,但通常为一个完整的实体对象,所以dao层接口中方法的参数要定义成一个实体对象。

dao层

int insert(BookType bookType);

sql映射文件

  • 通常加上parameterType参数指定添加的对象实体类全限定名
  • #{}中的参数一定来自于dao层接口方法实体参数对象的属性
  • 如果要在添加成功的同时,获取自动增长的主键值时,要添加
    • useGeneratedKeys="true" 表示自动获取自增的值
    • keyColumn="type_id" 表示自增字段
    • keyProperty="typeId" 表示自增字段对应的属性名
<insert id="testInsert" useGeneratedKeys="true" keyColumn="type_id" keyProperty="typeId"
        parameterType="com.hqyj.ssm02.entity.BookType">
    insert into book_type
    values (null, #{typeName})
</insert>

修改<update></update>

修改可以分为修改所有字段和修改部分字段

修改所有字段

dao层

int update(BookInfo bookInfo);

sql映射文件

<update id="update">
    update book_info set
    book_name = #{bookName},
    book_author = #{bookAuthor},
    book_num = #{bookNum},
    book_price = #{bookPrice},
    publisher_date = #{publisherDate}
    where book_id=#{bookId}
</update>

这种方式会修改所有字段,如果实体参数的某个属性没有赋值,就会用该属性的默认值进行修改。

如String类型的属性没有赋值,就会用null修改,int类型用0修改,如果表中该字段非空,就会导致sql执行异常。

所以修改所有字段,参数为一个完整对象时,保证其中的属性都有值。

修改部分字段

方式一:dao层只写要修改的字段

dao

int updateSth(int bookId,int bookPrice,int bookNum);

sql映射文件

<update id="updateSth">
    update book_info set
    book_num = #{bookNum},
    book_price = #{bookPrice}
    where book_id = #{bookId}
</update>

方式二:dao层写完整对象,sql语句中判断字段是否有值

dao

int updateSth(BookInfo bookInfo);

sql映射文件

<update id="updateSth">
    update book_info set
    <if test="bookPrice!=0">
        book_price = #{bookPrice},
    </if>
    <if test="bookName!=null">
        book_name=#{bookName}
    </if>
    where book_id = #{bookId}
</update>

这样写,有以下几个问题

1.如果只有第一个条件满足,后续条件都不满足,最终拼接的sql语句,会在where关键字之前多出一个逗号,导致语法错误

解决方式:将set替换成<set>标签,mybatis会自动去除最后的逗号

2.替换为<set>标签后,如果所有条件都不满足,mybatis会自动去除set部分,sql语句就会变为update book_info where book_id=?,导致语法错误

解决方式:在<set>标签中,添加一个不影响原始数据的条件,如 book_id = #{bookId},只需要一个book_id参数,sql语句没有语法错误,对原始数据没有任何影响

动态SQL

  • <set>搭配<if>用于修改

    <!--动态SQL:set-if用于修改-->
    <update id="testUpdate">
        update book_info
        <set>
            book_id=#{bookId},
            <if test="bookName!=null">
                book_name = #{bookName},
            </if>
            <if test="bookPrice!=null">
                book_price = #{bookPrice}
            </if>
        </set>
        where book_id=#{bookId}
    </update>
  • <where>搭配<if>用于查询、修改、删除时的条件

    <select id="queryByCondition" resultType="com.xxx.entity.BookInfo">
        select * from book_info
        <where>
            <if test="bookName!=null">
                book_name = #{bookName}
            </if>
            <if test="bookAuthor!=null">
                and book_author = #{bookAuthor}
            </if>
            <if test="typeId!=null">
                and type_id = #{typeId}
            </if>
        </where>
    </select>
  • <trim>搭配<if>可以替换set-if和where-if

    该标签有四个属性

    prefix 表示如果trim标签中有if条件满足,就在整个trim部分添加指定前缀

    suffix 表示如果trim标签中有if条件满足,就在整个trim部分添加指定后缀

    prefixOverrides 表示去除整个trim部分多余的前缀

    suffixOverrides 表示去除整个trim部分多余的后缀

使用trim实现修改

可以修改所有字段,也可以修改部分字段

<update id="testUpdate">
    update book_info
    <!--prefix="set"表示在所有内容前加入set关键字-->
    <!--suffixOverrides=","表示所有内容之后如果有多余的逗号,去掉逗号-->
    <trim prefix="set" suffixOverrides=",">
        book_id=#{bookId},
        <if test="bookName!=null">
            book_name = #{bookName},
        </if>
        <if test="bookAuthor!=null">
            book_author = #{bookAuthor},
        </if>
        <if test="bookNum!=null">
            book_num = #{bookNum},
        </if>
        <if test="bookPrice!=null">
            book_price = #{bookPrice},
        </if>
        <if test="publisherDate!=null">
            publisher_date = #{publisherDate}
        </if>
    </trim>
    where book_id=#{bookId}
</update>

使用trim标签实现多条件查询

<select id="queryByCondition" resultType="com.hqyj.ssm02.entity.BookInfo">
    SELECT bi.*,type_name as 'bookType.typeName' FROM book_info bi,book_type bt
    <!--prefix="where"表示在所有内容前加入where关键字-->
    <!--prefixOverrides="and"表示所有内容之前如果有多余的and,去掉and-->
    <!--suffix="order by book_id desc"表示在所有内容之后加入order by book_id desc-->
    <trim prefix="where" prefixOverrides="and" suffix="order by book_id desc">
        bi.type_id=bt.type_id
        <if test="bookName!=null">
            and book_name like concat ('%',#{bookName},'%')
        </if>
        <if test="bookAuthor!=null">
            and book_author like concat ('%',#{bookAuthor},'%')
        </if>
        <if test="typeId!=0">
            and bt.type_id =#{typeId}
        </if>
    </trim>
</select>

多选删除具体实现

页面核心js

$(function () {
    //一键全选按钮
    $("#checkAll").click(function () {
        var state = this.checked;
        $(".checkDel").each(function () {
            this.checked = state;
        });
    });
    //删除所选按钮
    $("#deleteAll").click(function () {
        //定义保存id的数组
        var ids = [];
        //获取当前被选中的复选框所在的tr
        let $tr = $(".checkDel:checked").parent().parent();
        //遍历所选的tr
        $tr.each(function () {
            //获取id对应的td的值
            var id = $(this).children("td:eq(1)").text();
            //保存到数组中
            ids.push(id);
        });
        //至少选中一项
        if (ids.length == 0) {
            alert("请至少选中一项");
            return;
        }
        if (!confirm("确认要删除这" + ids.length + "条数据吗")) {
            return;
        }
        //使用ajax访问controller删除所选
        $.ajax({
            url: "${pageContext.request.contextPath}/bookInfo/deleteByChecked",
            data: {
                "ids": ids
            },
            type: "post",
            //ajax提交数组,需要添加一个参数
            traditional: true,
            success: function () {
                location.reload();
            }
        });
    });
});

dao

//批量删除
int deleteByIds(@Param("idList") List<Integer> idList);

mapper.xml

<!--批量删除-->
<delete id="deleteByIds">
    delete from book_info where book_id in
    <foreach collection="idList" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</delete>

service

public void deleteByChecked(Integer[] ids) {
    //将数组转换为集合
    List<Integer> list = Arrays.asList(ids);
    bookInfoDao.deleteByIds(list);
}

controller

@RequestMapping("/deleteByChecked")
public String deleteByChecked(Integer[] ids){
    bookInfoService.deleteByChecked(ids);
    return "redirect:queryAll";
}

多条件查询具体实现

搜索表单

<form class="navbar-form navbar-left"
      action="${pageContext.request.contextPath}/bookInfo/queryByCondition">
    <div class="form-group">
        <input type="text" class="form-control" name="bookName" placeholder="请输入书名关键字">
        <input type="text" class="form-control" name="bookAuthor" placeholder="请输入作者关键字">
        <select id="topSelect" class="form-control" name="typeId">
            <option value="0">全部</option>
        </select>
    </div>
    <button type="submit" class="btn btn-default">搜索</button>
</form>

dao

List<BookInfo> queryByCondition(BookInfo bookInfo);

mapper.xml

<select id="queryByCondition" resultType="com.hqyj.ssm02.entity.BookInfo">
    SELECT bi.*,type_name as 'bookType.typeName' FROM book_info bi,book_type bt
    <trim prefix="where" prefixOverrides="and" suffix="order by book_id desc">
        bi.type_id=bt.type_id
        <if test="bookName!=null">
            and book_name like concat ('%',#{bookName},'%')
        </if>
        <if test="bookAuthor!=null">
            and book_author like concat ('%',#{bookAuthor},'%')
        </if>
        <if test="typeId!=0">
            and bt.type_id =#{typeId}
        </if>
    </trim>
</select>

service

public List<BookInfo> queryByCondition(BookInfo bookInfo) {
    return bookInfoDao.queryByCondition(bookInfo);
}

controller

@RequestMapping("/queryByCondition")
public String queryByCondition(BookInfo bookInfo,Model model) {
    List<BookInfo> list= bookInfoService.queryByCondition(bookInfo);
    model.addAttribute("list",list);
    return "bookInfoList";
}

分页

使用分页组件PageHelper

1.导入依赖

<!--分页组件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>

2.在mybatis的配置文件中

<!--设置分页插件-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--保证翻页不会超出范围-->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>

3.使用

  • 通过PageHelper类调用静态方法startPage(当前页数,每页显示的记录数)开启分页
  • 查询所有,返回集合
  • 创建PageInfo分页模型对象,构造方法的参数为查询出的集合,设置泛型,
//定义当前页和每页显示的数量
int pageNum=1;
int size=10;
//开启分页
PageHelper.startPage(pageNum,size);
//正常调用查询,返回查询到的数据集合
BookInfo bookInfo = new BookInfo();
bookInfo.setTypeId(1);
List<BookInfo> list = bookInfoDao.queryByCondition(bookInfo);

//创建分页模型对象,构造方法的参数为查询出的结果,设置泛型,
PageInfo<BookInfo> pageInfo = new PageInfo<>(list);

//此时分页相关数据都保存在pageInfo对象中
System.out.println("总记录数"+pageInfo.getTotal());
System.out.println("最大页数"+pageInfo.getPages());
System.out.println("当前页"+pageInfo.getPageNum());
System.out.println("当前容量"+pageInfo.getSize());
System.out.println("当前分页数据"+pageInfo.getList());
System.out.println("是否有上一页"+pageInfo.isHasPreviousPage());
System.out.println("是否有下一页"+pageInfo.isHasNextPage();
System.out.println("是否是首页"+pageInfo.isIsFirstPage());
System.out.println("是否是尾页"+pageInfo.isIsLastPage());
PageInfo对象常用属性和方法作用
total/getTotal()得到总记录数
pages/getPages()得到最大页数
pageNum/getPageNum()得到当前页
size/getSize()得到每页显示的记录数
list/getList()得到按当前page和size查询到的数据集合
isFirstPage/isIsFirstPage()是否是首页
isLastPage/isIsLastPage()是否是尾页

多条件分页具体实现

controller

@RequestMapping("/queryByCondition")
public String queryByCondition(
    @RequestParam(defaultValue = "1") int pageNum,
    @RequestParam(defaultValue = "8") int size,
    BookInfo bookInfo,
    Model model) {
    //1.PageHelper.startPage()
    PageHelper.startPage(pageNum, size);
    //2.查询集合
    List<BookInfo> list = bookInfoService.queryByCondition(bookInfo);
    //3.PageInfo(集合)
    PageInfo<BookInfo> pageInfo = new PageInfo<>(list);
    //将查询到的分页模型对象保存到model中
    model.addAttribute("pageInfo",pageInfo);

    //构造页数的集合
    ArrayList<Integer> pageList = new ArrayList<>();
    for (int i = 1; i <= pageInfo.getPages(); i++) {
        pageList.add(i);
    }
    model.addAttribute("pageList",pageList);

    return "bookInfoList";
}

页面分页组件

<nav aria-label="Page navigation">
    <ul class="pagination">
        <c:if test="${!pageInfo.isFirstPage}">
            <li>
                <a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum-1}"
                   aria-label="Previous">
                    <span aria-hidden="true">«</span>
                </a>
            </li>
        </c:if>
        <c:forEach items="${pageList}" var="pno">
            <li class="pno">
                <a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pno}">${pno}</a>
            </li>
        </c:forEach>
        <c:if test="${!pageInfo.isLastPage}">
            <li>
                <a href="${pageContext.request.contextPath}/bookInfo/queryByCondition?bookName=${param.bookName}&bookAuthor=${param.bookAuthor}&typeId=${param.typeId}&pageNum=${pageInfo.pageNum+1}"
                   aria-label="Next">
                    <span aria-hidden="true">»</span>
                </a>
            </li>
        </c:if>
    </ul>
</nav>

SpringBoot

Spring推出的一个Spring框架的脚手架。

不是一个新的框架,而是搭建Spring相关内容框架的平台。

它省去了Spring、SpringMVC项目繁琐的配置过程,让开发变得更加简单。

本质还是Spring+SpringMVC,可以搭配其他的ORM框架,如MyBatis、MyBatisPlus、JPA、Hibernate等。

特点

  • 内置了Tomcat服务器,不需要部署项目到Tomcat中
  • 内置了数据源Hikari
  • 减少了jar文件依赖的配置
  • SpringBoot中的配置文件可以使用yml格式文件,代替properties或xml

热部署

项目在开发过程中,可以不需要每次都重启,等待一段时间后会自动更新编译运行

使用

添加依赖,可以在创建的项目的时候选择,也可以中途添加

<!--热部署-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <version>2.7.8</version>
</dependency>

开启热部署

Lombok

用于简化实体类中模板代码的工具

使用

添加依赖,可以在创建的项目的时候选择,也可以中途添加

<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

安装插件(IDEA2020.2之后的版本会内置Lombok插件,无需安装)

在某个实体类上添加注解

lombok常用注解作用
@AllArgsConstructor自动生成全参构造方法
@Data以下注解之和
@Setter自动生成set方法
@Getter自动生成get方法
@NoArgsConstructor自动生成无参构造方法
@ToString自动生成toString方法
@EqualsAndHashcode自动生成equals和hashcode方法

SpringBoot+MyBatis实现单表查询

1.创建好SpringBoot项目

最好在创建的时候选择以下依赖

  • spring-web(必选)
  • lombok
  • spring-devtools
  • springboot集成mybatis
  • mysql驱动

MyBatisPlus

官网简介 | MyBatis-Plus (baomidou.com)

MyBatis-Plus (简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

只需简单的配置,就能实现对单表的CURD。

其核心有两个接口:BaseMapper和IService

BaseMapper中封装了大量数据访问层的方法

IServcie中封装了大量业务流程层的方法

SpringBoot+MyBatisPlus

1.创建SpringBoot项目

创建时勾选依赖

  • devtools
  • lombok
  • spring-web
  • mysql-driver

2.导入SpringBoot集成MyBatisPlus依赖

<!-- SpringBoot集成MyBatisPlus -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>

3.配置application.properties文件

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/gamedb?serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

# 开启sql日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# 无需加入开启驼峰命名映射,MyBatisPlus默认使用驼峰命名进行属性-字段映射
#mybatis-plus.configuration.map-underscore-to-camel-case=true

4.根据数据表创建实体类

实体类的属性名命名方式:

  • MyBatisPlus默认使用驼峰命名对字段和属性进行映射。如将字段stu_name对应的属性写为stuName
  • 如果字段名和属性名不一致,在属性名上加入*@TableField(value = "字段名")*
  • 主键字段对应的属性,需要加入@TableId注解,其type属性表示主键生成策略
    • @TableId(type = IdType.AUTO)表示主键自增,在数据库也要将主键设置为自增
    • @TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id
    • @TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串id
@Data
public class Hero {
    //type表示主键生成策略,
    @TableId(type = IdType.AUTO)// IdType.AUTO表示主键自增,在数据库也要将主键设置为自增
    //@TableId(type = IdType.ASSIGN_ID)//IdType.ASSIGN_ID表示使用"雪花算法"(根据时间和机器特征码)生成一个id
    //@TableId(type = IdType.ASSIGN_UUID)//IdType.ASSIGN_UUID表示使用UUID生成一个随机字符串id
    private Integer id;
    //如果属性名和字段名不一致
    @TableField(value = "name")
    private String heroName;
    private String position;
    private String sex;
    private Integer price;
    private String shelfDate;
}

5.编写数据访问层接口

可以不用写@Repository,继承BaseMapper接口,设置泛型

/*
* 数据访问层可以称为dao或mapper层
* 可以不用加@Repository注解
* */
public interface HeroMapper extends BaseMapper<Hero> {

}

6.在SpringBoot的启动类中,扫描数据访问层所在包

@SpringBootApplication
@MapperScan("com.hqyj.sbmp01.mapper")
public class Sbmp01Application {

    public static void main(String[] args) {
        SpringApplication.run(Sbmp01Application.class, args);
    }
}

测试

在SpringBoot自带的单元测试类中,注入HeroMapper对象,调用BaseMapper中定义的方法即可实现CURD。

BaseMapper接口

BaseMapper接口中定义了常用的增删改查方法,

在数据访问层接口中继承该接口

BaseMapper接口中的常用方法

方法名参数作用
selectList(Wrapper wrapper)条件构造器根据条件查询集合,如果实参为null表示查询所有,返回List集合
selectById(Serializable id)主键根据主键查询单个对象,返回单个对象
selectOne(Wrapper wrapper)条件构造器条件查询单个对象,返回单个对象
insert(T entity)实体对象添加单个实体
updateById(T entity)实体对象根据实体对象单个修改,对象必须至少有一个属性和主键
update(T entity,Wrapper wrapper)实体对象和条件构造器根据条件修改全部,对象必须至少有一个属性
deleteById(Serializable id/T entity)主键/实体对象根据主键删除单个对象
deleteBatchIds(Collection ids)主键集合根据集合删除
delete(Wrapper wrapper)条件构造器根据条件删除,如果实参为null表示无条件删除所有

测试

IService接口

IService接口减少业务逻辑层的代码,并对BaseMapper进行了拓展

在业务流程类中继承该接口

部分方法列表

使用

  • 1.创建一个业务层接口,继承IService接口,设置泛型

    /*
    * 创建业务逻辑层接口,继承IService<T>接口,设置泛型
    * */
    public interface HeroService extends IService<Hero> {
    }
  • 2.创建一个业务层接口的实现类,添加@Service注解,继承 ServiceImpl<M, T>,实现上一步创建的接口

    • M是数据访问层接口
    • T是实体类
    /*
    * 1.添加@Service
    * 2.继承ServiceImpl<M, T> M是Mapper类 T是实体类
    * 3.实现自定义Service接口
    * */
    @Service
    public class ImpHeroService extends ServiceImpl<HeroMapper, Hero> implements HeroService {
    
    }

IService接口中的常用方法

方法作用
list()无条件查询所有
list(Wrapper wrapper)条件查询素有
page(Page page)无条件分页查询,Page是分页模型对象
page(Page page,Wrapper wrapper)条件分页查询,Page是分页模型对象
getById(Serializable id)根据主键查询单个对象
getOne(Wrapper wrapper)条件查询单个对象
save(T entity)添加单个对象
save(Collection col)批量添加对象的集合
updateById(T entity)修改,参数至少有一个属性值和主键
saveOrUpdate(T entity)添加或修改。如果实参对象的主键值不存在则添加,存在则修改
update(T entity,Wrapper wrapper)条件修改,条件为null则修改全部数据
removeById(Serializable id/T entity)根据主键或包含主键的对象删除
removeBatchByIds(Collection ids)根据集合删除
remove(Wrapper wrapper)根据条件删除,条件为null则删除全部

条件构造器Wrapper

BaseMapper和IService接口中有很多方法都有这个参数,表示一个条件构造器对象。

如果该参数实际传递的值为null,表示没有任何条件,

这个Wrapper是一个抽象类,如果想要带条件,就要创建一个该类的子类对象。

常用子类为QueryWrapper和UpdateWrapper,

查询是创建QueryWrapper对象,更新时创建UpdateWrapper,实际使用无区别。

Wrapper对象带参数

Wrapper<T> wrapper =  new QueryWrapper(T entity);
QueryWrapper<T> wrapper =  new QueryWrapper(T entity);

Wrapper构造方法的参数如果是一个实体对象,只要该对象的属性不为空,就会将所有属性用and拼接起来作为条件。

分组和聚合函数(自定义查询)

分组通常会和聚合函数(count、sum、min、max、avg)一起使用,但MyBatisPlus中没有现成的聚合函数。

需要使用**select(String... fields)**自定义查询字段。

如查询每个每个位置的总数

条件构造器对象.select("position","count(id)"),对应的sql查询部分为select "position","count(id)" from hero

前后端分离项目

前后端分离,就是将web应用中的前端页面和后端代码分开完成、部署。

  • 前后端的开发者只需要完成各自的事情,最终以文档的形式约定数据接口(URL、参数、返回值、请求方式)
  • 前后端分别用独立的服务器
  • 后端只需处理数据并提供访问接口(路径),以RESTFul风格的JSON格式传输数据
  • 前端只需负责渲染页面和展示数据

传统项目和前后端分离项目对比

传统项目

前端和后端的代码运行在一个服务器上,页面经由控制器跳转

SSM项目、图书管理系统、答题系统

前后端分离项目

前后端的代码分别运行在各自的服务器上

后端提供JSON格式字符串的数据接口

前端负责跳转、解析JSON数据。

酒店客房管理系统

前后端分离项目后端控制层设计

请求方式设计:RESTFul风格

风格,不是标准,可以不用强制遵循。

RESTFul风格:用不同的请求方式去访问同一个URL地址时,执行不同的操作。

特点

  • 通过URL就能知道当前在哪个模块
  • 通过不同的请求方式决定执行什么操作
  • 通过返回的状态码得到操作结果

使用RESTFul风格和普通方式对比

普通方式

localhost:8080/user/queryAll                            查询所有
localhost:8080/user/queryById?id=1001                    条件查询
localhost:8080/user/insert?name=ez&sex=男&age=20            添加
localhost:8080/user/update?name=ez&id=1001                修改
localhost:8080/user/delete?id=1001                        删除

RESTFul风格

localhost:8080/user                            查询所有get请求
localhost:8080/user/1001                    条件查询get请求
localhost:8080/user                            添加post请求
localhost:8080/user                            修改put请求
localhost:8080/user/1001                    删除delete请求

RESTFul风格具体使用

  • 在请求映射的命名上,统一用小写字母的名词形式表示当前位于哪个模块。如/user、/book_info

  • 访问时如果要传参,使用"/模块名/参数"方式,配合controller中的@PathVariable获取

    @GetMapping("/book/{id}")
    public BookInfo queryById(@PathVariable("id")Integer id){
        return service.findById(id);
    }
  • 在controller的方法上,使用@XXXMapping()设置访问该方法的请求方式

    • @GetMapping("路径") 查询
    • @PostMapping("路径") 添加
    • *@PutMapping("路径") * 修改
    • *@DeleteMapping("路径") * 删除
    • @RequestMapping(value="路径",method=RequestMethod.GET/POST/PUT/DELETE))
  • 如果请求方式不匹配,会报405异常

  • 在同一个controller中,不能出现两个请求方式和路径都一致的方法

返回值设计

前后端分离项目的控制层方法的返回值也需要进行统一。

返回值通常包含以下信息

  • 传递状态,用状态码表示Integer code
  • 传递消息,用字符串表示String msg
  • 传递集合,用集合表示List list
  • 传递对象,用对象表示Object obj

将这些信息封装到一个对象中,这个对象称为返回结果类RestResult对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值