框架
一套规范。
实际是他人实现的一系列接口和类的集合。通入导入对应框架的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插件,无需安装)
-
IDEA插件官网Versions: Lombok - IntelliJ IDEs Plugin | Marketplace (jetbrains.com)
-
IDEA内置插件市场搜索
在某个实体类上添加注解
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对象。