目录
2.9.2 SpringMVC解析json:jackson、fastjson
一、Mybatis
Mybatis能够实现数据持久化:内存断电即失,数据库实现文件持久化。Mybatis帮助程序员将数据存入数据库中。Mybatis解除SQL和程序代码的耦合,通过编写XML降低了数据库操作的难度。
1.1 Mybatis环境搭建
1、创建数据库和表
CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE user;
CREATE TABLE `user`(
`id` INT(10) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` VARCHAR(100) NOT NULL COMMENT '名字',
`password` VARCHAR(200) NOT NULL COMMENT '密码',
KEY `id` (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`password`) VALUES
(1,'系统管理员','1234567'),
(2,'张三','111111'),
(3,'李四','222222');
2、新建普通maven项目,删除src目录(使之成为父项目Mybatis-Study),导入maven依赖(父项目pom.xml)
<!--导入依赖-->
<dependencies>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
3、为防止静态资源无法导出,添加以下语句
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
4、创建子模块,这个模块的父项目就是Mybatis-Study。编写mybatis核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>
<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://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/gx/dao/UserMapper.xml"/>
</mappers>
</configuration>
4、编写mybatis工具类
package com.gx.utils;
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;
//获取sqlSessionFactory ---> sqlSession
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用Mybatis第一步:获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
5、实体类、Dao接口
实体类使用lombok来简化代码,lombok可以通过注解的方式在编译时自动为属性生成构造器、getter/setter、toString等方法。使得代码更简洁。
package com.gx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private int id;
private String name;
private String password;
}
package com.gx.dao;
import com.gx.pojo.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
//查询全部用户
List<User> getUserList();
}
6、接口实现类变成一个Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<!--下面的mapper相当于实现接口的实现类-->
<!--mapper里面的id就是实现接口里面的方法-->
<mapper namespace="com.UserMapper">
<select id="getUserList" resultType="com.User">
select * from mybatis.user
</select>
</mapper>
7、在核心配置文件中注册mapper
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/gx/dao/UserMapper.xml"/>
</mappers>
8、Junit测试
package com.gx.dao;
import com.gx.pojo.User;
import com.gx.utils.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapperTest {
@Test
public void test() {
//1、获得sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
//2、执行(方式1:getMapper)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
//关闭sqlSession
sqlSession.close();
}
}
9、结果
User(id=1, name=系统管理员, password=1234567)
User(id=5, name=韩路彪, password=0000000)
User(id=100, name=张三, password=12211212)
Process finished with exit code 0
10、可能遇到的问题
①配置文件未注册:报配置文件找不到错误
②绑定接口错误:找不到错误
③方法名不对:找不到错误
④返回类型不对
⑤maven资源导出问题
⑥某个属性为空:实体类属性和表的属性没有对应
1.2 CRUD
1.2.1 select
namespace | 包名要和接口的包名一致 |
id | 对应上述接口的方法名 |
resultType | SQL语句执行返回值的属性 |
parameterType | 参数类型 |
1、编写接口
//查询全部用户
List<User> getUserList();
//根据ID查询用户
User getUserById(int id) ;
2、在mapper中编写相应的sql语句
<select id="getUserList" resultType="com.gx.pojo.User">
select * from mybatis.user
</select>
<select id="getUserById" resultType="com.gx.pojo.User" parameterType="int">
select * from mybatis.user where id = #{id}
</select>
3、测试
@Test
public void test() {
//1、获得sqlSession对象
SqlSession sqlSession = MybatisUtil.getSqlSession();
//2、执行(方式1:getMapper)
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
//关闭sqlSession
sqlSession.close();
}
@Test
public void getUserById() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
1.2.2 insert update delete
注意:因为增删改修改了数据库,所以需要提交事务
1、接口
//insert一个用户
int addUser(User user);
//修改用户
int updateUser(User user);
//删除一个用户
int deleteUser(int id);
2、XML中的SQL语句
<!--对象中的属性可以直接取出来-->
<insert id="addUser" parameterType="com.gx.pojo.User">
insert into mybatis.user (id,name,password)
values (#{id},#{name},#{password});
</insert>
<update id="updateUser" parameterType="com.gx.pojo.User">
update mybatis.user
set name = #{name},password = #{password}
where id = #{id};
</update>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id = #{id};
</delete>
3、测试
//增删改需要提交事务(改变数据库中的记录就要提交事务)
@Test
public void addUser() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.addUser(new User(4,"哈哈", "121212"));
if(res > 0) {
System.out.println("插入成功");
}
//提交事务
sqlSession.commit();
sqlSession.close();
}
@Test
public void updateUser() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.updateUser(new User(4, "呜呜", "444444"));
if(res > 0) {
System.out.println("修改成功");
}
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(4);
if(i > 0) {
System.out.println("删除成功");
}
sqlSession.commit();
sqlSession.close();
}
1.2.3 万能Map
当一个实体类的属性过多时,对一些参数进行修改的话,重新new一个对象就会很麻烦,可以使用map指定一些属性进行修改
1、接口
//万能的map
int addUser2(Map<String,Object> map);
User getUserById2(Map<String,Object> map);
2、XML
<!--传递map中的key-->
<insert id="addUser2" parameterType="map">
insert into mybatis.user (id,name ,password) values (#{id},#{name},#{password});
</insert>
<select id="getUserById2" parameterType="map" resultType="com.gx.pojo.User">
select * from mybatis.user where id = #{userId};
</select>
3、测试
@Test
public void addUser2() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map= new HashMap<String, Object>();
map.put("id",6);
map.put("name","hello");
map.put("password","555555");
int i = mapper.addUser2(map);
sqlSession.commit();
sqlSession.close();
}
@Test
public void getUserById2() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("userId",6);
User user = mapper.getUserById2(map);
System.out.println(user);
}
1.2.4 模糊查询
1、Java代码部分,参数传递使用通配符%%
2、在SQL拼接中使用通配符 CONCAT('%',#{param},'%')
//模糊查询
List<User> getUsers(String value);
<!--模糊查询-->
<select id="getUsers" resultType="com.gx.pojo.User" parameterType="String">
select * from mybatis.user where name like CONCAT('%',#{name},'%')
</select>
@Test
public void getUsers() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers("%韩%");
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
1.3 Mybatis配置文件解析
1.3.1 属性(Properties)
可以通过properties属性来实现引用配置文件。这些属性可以在外部进行配置,并且可以进行动态替换。
1、编写配置文件db.properties
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
username = root
password = 123456
2、在核心配置文件中引入(核心配置文件中的优先级小于外部配置文件中的优先级)
<!--引入外部配置文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
3、设置环境
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
1.3.2 类型别名(typeAliases)
com.gx.pojo.User太长了,可以使用类型别名来设置一个缩写名字
<typeAliases>
<typeAlias type="com.gx.pojo.User" alias="user"/>
</typeAliases>
也可以指定一个包,那么这个包下的实体类的默认别名就是这个类的类名首字母小写,也可以通过注解指定类的别名。
<typeAliases>
<package name="com.gx.pojo"/>
</typeAliases>
在实体类较少的情况下,使用第一种方式,如果实体类比较多,建议使用第二种。第一种可以DIY别名,第二种则不行,但是在使用第二种的前提下可以在实体类上使用注解来设置别名
package com.gx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.ibatis.type.Alias;
@Alias("user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
1.3.3 其它配置
1、设置(settings):改变Mybatis的运行时行为
2、typeHandlers:类型处理器
3、objectFactory:对象工厂
4、plugins插件(maven中添加)
- mybatis-generator-core
- mybatis-plus
- 通用mapper
1.3.4 映射器(mappers)
MapperRegistry:用于绑定Mapper文件
方式一【推荐使用】:
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册-->
<mappers>
<mapper resource="com/gx/dao/UserMapper.xml"/>
</mappers>
方式二:使用class文件绑定注册
<mappers>
<mapper class="com.gx.dao.UserMapper"/>
</mappers>
方式三:使用包扫描进行注入(无需注解)
<mappers>
<package name="com.gx.dao"/>
</mappers>
1.4 数据库表和实体类没有对应
例如数据库表的字段pwd和实体类属性password不一致,那么获取的密码就为null。
User(id=1, name=系统管理员, password=null)
User(id=5, name=韩路彪, password=null)
User(id=6, name=hello, password=null)
User(id=100, name=张三, password=null)
Process finished with exit code 0
解决方法:
1、起别名
<select id="getUserById" resultType="user" parameterType="int">
select id,name,pwd as password from mybatis.user where id = #{id}
</select>
2、结果集映射(ResultMap)
结果集映射可以指定数据库表字段(column)和实体类属性(property)的对应关系
<!--结果集映射-->
<resultMap id="UserMap" type="user">
<!--column数据库中的字段,property实体类中的属性-->
<!--<result column="id" property="id"/>
<result column="name" property="name"/>-->
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id = #{id}
</select>
1.5 日志
进行数据库操作时出现了异常,需要排错,日志就是最好的助手。
1.5.1 日志工厂
常见的日志工厂:
- SLF4J
- LOG4J【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING
- NO_LOGGING
日志的配置:mybatis核心配置文件中进行配置。在properties后配置
<settings>
<!--标准的日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!--<setting name="logImpl" value="LOG4J"/>-->
</settings>
输出
Opening JDBC Connection
Created connection 1704629915.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@659a969b]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, 系统管理员, 1234567
<== Total: 1
User{id=1, name='系统管理员', password='1234567'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@659a969b]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@659a969b]
Returned connection 1704629915 to pool.
1.5.2 Log4j
Log4j是Apache的一个开源项目,通过使用Log4j,可以控制日志信息输送的目的地是控制台、文件、GUI组件;也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,可以更加细致地控制日志的生成过程;这些都可以通过一个配置文件来灵活的进行配置,而不需要修改业务代码。
1、导入log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/gx.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
3、配置log4j为日志的实现
<settings>
<!--标准的日志工厂实现-->
<!--<setting name="logImpl" value="STDOUT_LOGGING"/>-->
<setting name="logImpl" value="LOG4J"/>
</settings>
4、简单使用
创建日志对象
static Logger logger = Logger.getLogger(UserMapperTest.class);
测试
@Test
public void testLog4j() {
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");
}
测试结果
[com.gx.dao.UserMapperTest]-info:进入了testLog4j
[com.gx.dao.UserMapperTest]-debug:进入了testLog4j
[com.gx.dao.UserMapperTest]-error:进入了testLog4j
Process finished with exit code 0
1.6 分页
使用分页的原因:减少数据的处理量
1.6.1 使用limit分页
语法:从startIndex开始显示,显示pageSize个
select * from mybatis.user limit startIndex,pageSize
只有一个参数n,那么显示[0,n)条数据。
使用Mybatis实现分页:
- 接口
//分页
List<User> getUserByLimit(Map<String,Integer> map);
- mapper.xml
<!--分页-->
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
- 测试
@Test
public void testLimit() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",0);
map.put("pageSize",3);
List<User> users = mapper.getUserByLimit(map);
for (User user : users) {
System.out.println(user);
}
}
1.6.2 RowsBounds(了解)
1、接口
//分页2
List<User> getUserByRowBounds();
2、mapper.xml
<!--分页2-->
<select id="getUserByRowBounds" resultMap="UserMap" >
select * from mybatis.user
</select>
3、测试
@Test
public void TestRowBounds() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
//RowBounds实现分页
RowBounds rowBounds = new RowBounds(1,2);
//通过java代码层实现分页
List<User> users = sqlSession.selectList("com.gx.dao.UserMapper.getUserByRowBounds",null,rowBounds);
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
1.6.3 分页插件(了解)
https://pagehelper.github.io/
1.7 注解
1.7.1 注解开发
SQL语句可以通过注解完成,完成简单的SQL语句
- 在核心配置文件中绑定接口
<!--绑定接口-->
<mappers>
<mapper class="com.gx.dao.UserMapper"/>
</mappers>
- 在接口上添加注解
@Select("select id,name,pwd as password from user")
List<User> getUsers();
- 测试
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
//底层主要运用反射
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
sqlSession.close();
}
}
1.7.2 注解CURD
修改工具类使得满足可自动提交事务
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);
}
方法中存在多个参数,使用@param进行注解,与SQL中保持一致,而与后面的参数名无关
@Select("select * from user where id = #{id}")
User getUser(@Param("id") int id1111);
其余
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id=#{id}")
int deleteUser(@Param("id") int id);
1.7.3 @Param、#{}和${}
1、@Param()
- 基本数据类型的参数或者String类型需要使用@Param
- 引用类型不需要
- 只有一个基本类型的参数时可以忽略,但是建议加上
- SQL中引用的就是@Param中设定的属性名
2、#{}和${}的区别
- #{}用来传入参数,SQL在解析时会加上"",当作字符串来解析
- #{}能够很大程度上防止SQL注入
- ${}传入的数据会直接显示在生成的SQL中,执行会出错
- ${}无法防止SQL注入
- 能够使用#{},尽量使用#{}
1.8 Lombok
Lombok能以简单的注解形式来简化Java代码,提高开发人员的开发效率。
使用步骤:
- 安装Lombok插件
- 使用时pom.xml中导入依赖
- 在实体类上加注解即可
1.9 多对一的处理
多个学生(id,name,tid)对应一个老师(id,name),tid对应id
select s.id,s.name,t.name from student s,teacher t where s.tid = t.id;
实体类
package com.gx.pojo;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}
package com.gx.pojo;
import lombok.Data;
@Data
public class Teacher {
private int id;
private String name;
}
直接查询
public List<Student> getStudent();
<select id="getStudent" resultType="Student">
select * from mybatis.student;
</select>
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper1 = sqlSession.getMapper(StudentMapper.class);
List<Student> student1 = mapper1.getStudent();
for (Student student : student1) {
System.out.println(student);
}
sqlSession.close();
}
发现teacher属性为空。
Student(id=1, name=小明, teacher=null)
Student(id=2, name=小红, teacher=null)
Student(id=3, name=小张, teacher=null)
Student(id=4, name=小李, teacher=null)
Student(id=5, name=小王, teacher=null)
解决思路:首先查询所有的学生信息,然后根据查出来的学生的tid寻找对应的老师。实现方法有以下两种。
1.9.1 子查询(按照查询嵌套处理)
子查询思路:首先一条SQL获取一些属性,直接获取不到的属性通过另一条SQL来获取。直接获取不到的属性通常是对象或者集合,对象使用association来处理,集合使用collection来处理。
public List<Student> getStudent2();
<select id="getStudent2" resultMap="StudentTeacher">
select * from mybatis.student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--复杂的属性,需要单独处理
对象使用 association
集合使用 collection
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher where id = #{tid};
</select>
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper1 = sqlSession.getMapper(StudentMapper.class);
//方法1 子查询
List<Student> student2 = mapper1.getStudent2();
for (Student student : student2) {
System.out.println(student);
}
}
结果
Student(id=1, name=小明, teacher=Teacher(id=1, name=秦老师))
Student(id=2, name=小红, teacher=Teacher(id=1, name=秦老师))
Student(id=3, name=小张, teacher=Teacher(id=1, name=秦老师))
Student(id=4, name=小李, teacher=Teacher(id=1, name=秦老师))
Student(id=5, name=小王, teacher=Teacher(id=1, name=秦老师))
1.9.2 联表查询(按照结果嵌套查询)
联表查询思路:一条完整的SQL来实现全部的功能。
public List<Student> getStudent3();
<select id="getStudent3" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.id tid,t.name tname
from mybatis.student s,mybatis.teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper1 = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper1.getStudent3();
for (Student student : students) {
System.out.println(student);
}
sqlSession.close();
}
结果
Student(id=1, name=小明, teacher=Teacher(id=1, name=秦老师))
Student(id=2, name=小红, teacher=Teacher(id=1, name=秦老师))
Student(id=3, name=小张, teacher=Teacher(id=1, name=秦老师))
Student(id=4, name=小李, teacher=Teacher(id=1, name=秦老师))
Student(id=5, name=小王, teacher=Teacher(id=1, name=秦老师))
1.10 一对多的处理
一个老师有多个学生,获取一个老师下的所有学生
package com.gx.pojo;
import lombok.Data;
import java.util.List;
@Data
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
List<Teacher> getTeacher();
<select id="getTeacher" resultType="Teacher">
select * from mybatis.teacher;
</select>
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
List<Teacher> teachers = mapper.getTeacher();
for (Teacher teacher : teachers) {
System.out.println(teacher);
}
sqlSession.close();
}
结果
Teacher(id=1, name=秦老师, students=null)
发现学生列表为空。思路:首先根据tid获取老师,再获取所有学生。
注意:学生是多个,使用集合来存储collection。
1.10.1 子查询(按照查询嵌套处理)
Teacher getTeacher2(@Param("tid") int id);
id、name是一一对应的,可以不用写,只写List<Student>属性
<select id="getTeacher2" resultMap="TeacherStudent2">
select *
from mybatis.teacher
where id = #{tid};
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" javaType="ArrayList" ofType="Students" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from mybatis.student where tid = #{tid};
</select>
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher2(1);
System.out.println(teacher);
sqlSession.close();
}
结果
Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
Process finished with exit code 0
1.10.2 联表查询(按照结果嵌套处理)
Teacher getTeacher1(@Param("tid") int id);
<select id="getTeacher1" resultMap="TeacherStudent">
select s.id sid,s.name sname,t.name tname,t.id tid
from mybatis.teacher t,mybatis.student s
where s.tid = t.id and t.id = #{tid};
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--复杂的属性,我们需要单独处理对象:association 集合:collection
javaType=""指定属性的类型
集合中的泛型信息,我们使用ofType获取
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
@Test
public void test() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher2(1);
System.out.println(teacher);
sqlSession.close();
}
结果
Teacher(id=1, name=秦老师, students=[Student(id=1, name=小明, tid=1), Student(id=2, name=小红, tid=1), Student(id=3, name=小张, tid=1), Student(id=4, name=小李, tid=1), Student(id=5, name=小王, tid=1)])
Process finished with exit code 0
1.10.3 小结
关联:association【多对一】
集合:collection【一对多】
javaType和ofType
- javaType:用来指定实体类中属性的类型
- ofType:用来指定映射到List或者集合中的pojo类型,泛型中的约束类型
1.11 动态SQL
动态SQL就是根据不同的条件生成不同的SQL语句。
- if
- choose(when,otherwise)
- trim(where,set)
- foreach
实体类
package com.gx.pojo;
import lombok.Data;
import java.util.Date;
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime; //属性名和字段名不一致(_命名到驼峰命名)
private int views;
}
核心配置文件中,开启驼峰命名自动映射
<settings>
<!--从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
接口和xml
int addBlog(Blog blog);
<insert id="addBlog" parameterType="Blog">
insert into mybatis.blog (id,title,author,create_time,views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
测试
@Test
public void addInitBlog() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtils.getId());
blog.setTitle("Mybatis如此简单");
blog.setAuthor("狂神说");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("Java如此简单");
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("Spring如此简单");
mapper.addBlog(blog);
blog.setId(IDUtils.getId());
blog.setTitle("微服务如此简单");
mapper.addBlog(blog);
sqlSession.close();
}
1.11.1 if
List<Blog> queryBlogIf(Map map);
<select id="queryBlogIf" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
@Test
public void testIf() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title","Mybatis如此简单");
// map.put("author","狂神说");
List<Blog> blogs = mapper.queryBlogIf(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
结果
Blog(id=735b3ec0f4c244149c481d7067420a82, title=Mybatis如此简单, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Process finished with exit code 0
1.11.2 choose
类似于switch语句
List<Blog> queryBlogByChoose(Map map);
<select id="queryBlogByChoose" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
views = #{views}
</otherwise>
</choose>
</where>
</select>
@Test
public void testIf() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("views",9999);
List<Blog> blogs = mapper.queryBlogByChoose(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
结果
Blog(id=735b3ec0f4c244149c481d7067420a82, title=Mybatis如此简单, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Blog(id=1404cd3c4116452cbc07412c1a9a0ece, title=Java如此简单, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Blog(id=22c4b13d72cb4462ac6d28438b5e00af, title=Spring如此简单, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Blog(id=967113588a4f4429b1b40c770f9a85b2, title=微服务如此简单, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Process finished with exit code 0
choose和if的区别:choose相当于switch case,会执行其中的一条语句(可以不加and),而if可能会执行0个条件,也可能结合多个条件,需要加and。
1.11.3 trim(where,set)
<where>只会在子句返回内容是才插入where语句,而且子句开头是"and"或者"or",where元素也会将其去除。而直接使用where时,若子句开头是and或者or时会报错。
<set>元素会动态地在行首插入set关键字,并且删除额外的逗号
int updateBlog(Map map);
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
where id = #{id}
</update>
@Test
public void testSet() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("id","62b9760688c94742a55c453cc9732fe7");
map.put("title","Mybatis如此简单2");
int res = mapper.updateBlog(map);
sqlSession.close();
}
1.11.4 foreach
动态SQL的另一个常见应用场景是对集合进行遍历
List<Blog> queryBlogForeach(Map map);
select * from mybatis.blog where (id = 1) or (id = 2) or (id=3)
<select id="queryBlogForeach" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<foreach collection="titles" item="title" open="(" close=")" separator="or">
title = #{title}
</foreach>
</where>
</select>
@Test
public void testForeach() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap<String, List<String>> map = new HashMap<String, List<String>>();
ArrayList<String> list = new ArrayList<String>();
list.add("Mybatis如此简单2");
list.add("Java如此简单");
list.add("Spring如此简单");
map.put("titles",list);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
结果
Blog(id=735b3ec0f4c244149c481d7067420a82, title=Mybatis如此简单2, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Blog(id=1404cd3c4116452cbc07412c1a9a0ece, title=Java如此简单, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Blog(id=22c4b13d72cb4462ac6d28438b5e00af, title=Spring如此简单, author=狂神说, createTime=Mon Aug 30 21:45:19 CST 2021, views=9999)
Process finished with exit code 0
1.11.5 SQL片段
有时会将功能的一部分抽取出来,方便复用
- 使用sql标签抽取公共的部分
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
- 在需要使用的地方使用include标签引用即可
<select id="queryBlogIf" parameterType="map" resultType="Blog">
select * from mybatis.blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
1.12 缓存
查询时需要连接数据库,比较耗费资源。使用缓存将一次查询的结果暂存起来,当再次查询相同数据的时候,直接走缓存,就不用走数据库,这样比较省资源。
缓存:
- 存在内存中的临时数据
- 将经常用到的数据放入缓存(内存)中,用户去查询数据就不要从磁盘(关系型数据库)中查询,提高了查询效率,解决了高并发系统的性能问题
- 使用缓存的原因:减少与数据库的交互,减小系统开销,提高系统效率
- 什么样的数据可以缓存:经常查询而不经常改变的数据
1.12.1 Mybatis缓存
Mybatis中默认定义了两种缓存:一级缓存和二级缓存
- 默认情况下,只开启一级缓存:sqlSesssion级别的缓存,也称为本地缓存
- 二级缓存需要手动开启和配置,是基于namespace级别的缓存
- 为了提高可扩展性,Mybatis定义了缓存接口Cache。可以通过实现Cache接口来定义二级缓存
1.12.2 一级缓存
一级缓存也称为本地缓存:SqlSession
- 与数据库头一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
测试:在同一个Session中查询两次相同记录
@Test
public void test1() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
User user2 = mapper.queryUserById(1);
System.out.println(user == user2);
sqlSession.close();
}
结果为true
缓存失效的情况:
- 查询不同的东西
- 增删改操作,可能会改变原来的数据,所以会刷新缓存
@Test
public void test1() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(6);
mapper.updateUser(new User(6,"hi","12334"));
User user2 = mapper.queryUserById(6);
System.out.println(user == user2);
sqlSession.close();
}
结果是false
- 查询不同的mapping.xml
- 手动清理缓存 sqlSession.clearCache()
@Test
public void test1() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(6);
sqlSession.clearCache();
User user2 = mapper.queryUserById(6);
System.out.println(user == user2);
sqlSession.close();
}
结果是false
1.12.3 二级缓存
- 二级缓存又称全局缓存,一级缓存作用域太低了,所以产生了二级缓存
- 基于namespace级别的缓存,一个名称空间对应一个二级缓存
- 工作机制:会话关闭了,一级缓存没了,使用二级缓存将一级缓存中的数据存入二级缓存中。
使用步骤:
- 开启全局缓存
<settings>
<!--显示的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 在要使用二级缓存的mapper中开启二级缓存
<!--在当前mapper.xml中使用二级缓存-->
<!--可以自定义一些参数-->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
- 测试
@Test
public void test2() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
sqlSession.close();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.queryUserById(1);
System.out.println(user.hashCode());
System.out.println(user2.hashCode());
System.out.println(user == user2);
sqlSession2.close();
}
结果为true
小结
- 只要开启了二级缓存,在同一个mapper下就有效
- 所有的数据都会先存放在一级缓存
- 只要当会话提交或者关闭时,才会提交到二级缓存中
查询顺序:二级缓存一级缓存
数据库
1.12.4 自定义缓存ehcache
1、导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
2、在mapperx.xml中指定使用ehcache实现缓存
<!--在当前Mapper.xml中使用第三方缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
3、配置ehcache.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
二、Spring
官方文档:https://docs.spring.io/spring-framework/docs/5.3.0/reference/html/core.html#spring-core
中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/core.html
Github:https://github.com/spring-projects/spring-framework
Spring的优点:
- 是开源免费的框架,容器
- 轻量级的框架,非侵入式的:使用Spring不会对原来的项目造成影响
- 控制反转IOC,面向切片AOP
- 支持事务的处理,对框架整合的支持
- ......
2.1 IOC理论推导
2.1.1 IOC理论实现的简单例子
- UserDao:接口
- UserDaoImpl:实现类
- UserService:业务接口
- UserServiceImpl:业务实现类
在之前的业务中,用户的需求可能会影响代码,需要根据用户的需要去修改原代码,修改一次的成本代价是否昂贵。对象是程序员主动创建的,使用set注入属性,使得对象被动接受,从而使得程序员不需要管对象的创建,使得系统的耦合性大大降低,从而程序员可以更加专注的在业务的实现上。这就是IOC的原型。
2.1.2 IOC本质
控制反转IOC(Inversion of Control)是一种设计思想,依赖注入DI是实现IOC的一种方法。没有IOC的程序中,我们面向对象编程,对象的创建和对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制;控制反转后将对象的创建转移给第三方。
IOC是Spring框架的核心内存,使用多种方式完美实现了IOC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IOC。Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象(Bean)。
使用XML方式配置Bean时,Bean的定义信息是和实现分离的,而使用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类上。
控制反转是一种通过描述(XML或注解),并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入
2.1.3 HelloSpring
1、导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
2、实体类
package com.gx.pojo;
public class Hello {
private String str;
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
}
3、beams.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中,这些都称为bean-->
<!--bean就是java对象 , 由Spring创建和管理
类型 变量名 = new 类型()
id == 变量名
class = new的对象
bean == 对象 new Hello()
property 相当于给对象中的属性设置一个值
-->
<bean id="hello" class="com.gx.pojo.Hello">
<property name="str" value="Spring"/>
</bean>
</beans>
4、测试
import com.gx.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
//获取Spring的上下文对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//我们的对象现在都在Spring中的管理了,我们要使用,直接去里面取出来即可
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.toString());
}
}
5、结果
Hello{str='Spring'}
Process finished with exit code 0
hello对象是由Spring创建的,对象的属性是由Spring容器设置的。这个过程就是控制反转(不用修改Java代码,只需要修改xml文件就可以修改对象)
控制:对象的创建由Spring控制
反转:程序本身不创建对象,而是变成被动的接收对象
依赖注入:利用set方法进行注入
2.1.4 IOC创建对象的方式
1、使用无参构造创建对象(默认)
<bean id="user" class="com.gx.pojo.User">
<property name="name" value="张三"/>
</bean>
2、使用有参构造创建对象
- 根据下标赋值
- 根据类型赋值
- 直接根据参数名来设置
<!--有参构造-->
<!--1、下标赋值-->
<bean id="user1" class="com.gx.pojo.User">
<constructor-arg index="0" value="李四"/>
</bean>
<!--2、根据类型赋值,不建议使用-->
<bean id="user2" class="com.gx.pojo.User">
<constructor-arg type="java.lang.String" value="王五"/>
</bean>
<!--3、直接通过参数名来设置-->
<bean id="suer3" class="com.gx.pojo.User">
<constructor-arg name="name" value="陈六"/>
</bean>
在配置文件加载的时候,容器中管理的对象就已经初始化了
2.1.5 Spring配置
1、别名,两种方式
设置了别名之后,既可以通过bean的id获取对象,也可以通过别名获取对象
<!--给对象起别名-->
<alias name="user" alias="uuu"/>
<!--name也是别名-->
<bean id="user4" class="com.gx.pojo.User" name="u1 u2,u3;u4">
<constructor-arg name="name" value="陈六六"/>
</bean>
2、bean的配置
id:bean的唯一标识符,相当于对象名
class:bean对象所对应的全限定名:包名+类名
name:别名,name可以同时起多个别名,可以用逗号、空格、分号等隔开,每个别名都可用以获取对象
3、import
一般用于团队开发,可以将多个人开发的beans整合到一个总的xml文件中。项目中有多个人开发,每个人负责不同的类开发,那么不同的类就注册在不同的beans.xml中,可以使用import将所有人的beans合并成一个。使用的时候直接只要总的配置就可以了。
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
2.1.5 依赖注入
注入对象的属性:
- 构造器注入【前面已提】
- set方法注入【重点】:依赖注入(bean对象由容器创建,其所有属性由容器注入)
2.1.6 各种数据类型的属性的注入
1、实体类
package com.gx.pojo;
public class Address {
private String addresss;
public String getAddresss() {
return addresss;
}
public void setAddresss(String addresss) {
this.addresss = addresss;
}
@Override
public String toString() {
return "Address{" +
"addresss='" + addresss + '\'' +
'}';
}
}
package com.gx.pojo;
import java.util.*;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public String getName() {
return name;
}
public Address getAddress() {
return address;
}
public String[] getBooks() {
return books;
}
public List<String> getHobbys() {
return hobbys;
}
public Map<String, String> getCard() {
return card;
}
public Set<String> getGames() {
return games;
}
public String getWife() {
return wife;
}
public Properties getInfo() {
return info;
}
public void setName(String name) {
this.name = name;
}
public void setAddress(Address address) {
this.address = address;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setWife(String wife) {
this.wife = wife;
}
public void setInfo(Properties info) {
this.info = info;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", address=" + address.toString() +
", books=" + Arrays.toString(books) +
", hobbys=" + hobbys +
", card=" + card +
", games=" + games +
", wife='" + wife + '\'' +
", info=" + info +
'}';
}
}
2、beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.gx.pojo.Address">
<property name="addresss" value="南京"/>
</bean>
<!--依赖注入:属性注入,属性有多种类型-->
<bean id="student" class="com.gx.pojo.Student">
<!--1、普通类型(String)注入value-->
<property name="name" value="张三"/>
<!--2、自定义类类型注入ref-->
<property name="address" ref="address"/>
<!--3、数组注入-->
<property name="books">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!--4、List类型注入-->
<property name="hobbys">
<list>
<value>听歌</value>
<value>敲代码</value>
<value>看电影</value>
</list>
</property>
<!--5、Map类型注入-->
<property name="card">
<map>
<entry key="身份证" value="1234567890"/>
<entry key="银行卡" value="0987654321"/>
</map>
</property>
<!--6、Set类型注入-->
<property name="games">
<set>
<value>LOL</value>
<value>COC</value>
<value>BOB</value>
</set>
</property>
<!--7、null注入-->
<property name="wife">
<null/>
</property>
<!--8、Properties注入-->
<property name="info">
<props>
<prop key="driver">123456</prop>
<prop key="gender">男</prop>
<prop key="username">root</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>
3、测试
@Test
public void test1() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Student student = (Student) context.getBean("student");
System.out.println(student.getAddress());
System.out.println(student.toString());
}
2.1.7 拓展方式注入
- p命名空间注入
- c命名空间
- (均只适合简单数据类型)
需要导入xml约束xmlns:p或者xmlns:c
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--p命名空间注入,可以直接注入属性的值:property-->
<bean id="user" class="com.gx.pojo.User" p:name="张三" p:age="18"/>
<!--c命名空间注入:通过构造器注入-->
<bean id="user2" class="com.gx.pojo.User" c:name="李四" c:age="20" scope="prototype"/>
</beans>
测试
@Test
public void test2() {
ApplicationContext context1 = new ClassPathXmlApplicationContext("userbeans.xml");
User user = (User) context1.getBean("user");
User user2 = context1.getBean("user2", User.class);
User user3 = context1.getBean("user2", User.class);
System.out.println(user.toString());
System.out.println(user2);
System.out.println(user2 == user3);
}
bean的作用域:
- 默认是单例模式singleton:共享一个对象(不适合于多线程场景)
- 原型模式prototype:每次从容器中get的时候,都会产生一个新的对象,浪费资源
- request、session、application在web开发中用到
2.1.8 bean的自动装配(Autowire)
bean的自动装配:Spring会在上下文中自动寻找并自动给bean装配属性
Spring中装配属性的方式:
- 在xml中显式的装配【已提】
- Java中显式装配
- 隐式的自动装配【重要】
1、byName自动装配
byName:会自动在容器上下文中查找和该对象set方法中参数名相一致的bean id
private Cat cat;
private Dog dog;
private String name;
public void setCat(Cat cat) {
this.cat = cat;
}
public void setDog(Dog dog) {
this.dog = dog;
}
<bean id="cat" class="com.gx.pojo.Cat"/>
<bean id="dog" class="com.gx.pojo.Dog"/>
<bean id="people" class="com.gx.pojo.People" autowire="byName">
<property name="name" value="张三"/>
</bean>
2、byType自动装配
byType:会自动在容器上下文查找和自己对象set方法中类型相一致参数的bean。缺点是一个类只能确定一个bean
<bean id="cat" class="com.gx.pojo.Cat"/>
<bean id="dog" class="com.gx.pojo.Dog"/>
<bean id="people" class="com.gx.pojo.People" autowire="byType">
<property name="name" value="张三"/>
</bean>
3、小结
- byName:需要保证所有的bean的id唯一,并且这个bean id需要和自动注入的属性的set方法参数名一致
- byType:需要保证所有的bean的class唯一,并且这个bean需要和自动注入的属性类型一致
2.1.9 使用注解实现自动装配
准备:
- 导入context约束
- 配置注解的支持<context:annotation-config>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解支持-->
<context:annotation-config/>
<bean id="cat" class="com.gx.pojo.Cat"/>
<bean id="dog" class="com.gx.pojo.Dog"/>
<bean id="dog111" class="com.gx.pojo.Dog"/>
<bean id="people" class="com.gx.pojo.People"/>
</beans>
在属性上加注解@Autowired,也可以在set方法上加。使用了@Autowired之后,就可以不编写set方法了,但前提是自动装配的属性在IOC容器中存在,且id符合byName
@Autowired
private Cat cat;
@Autowired
@Qualifier(value = "dog111") //将对应bean的id为dog变成id为dog111
private Dog dog;
private String name;
补充:
- 在set方法中对参数加注解@Nullable,表明这个属性可以是null
- 对属性加注解@Qualifier(value="xxx"):可以指定id为xxx的bean
- @Resource:是Java库下的,首先通过名字查找,找不到在通过类型查找,再找不到就报错
- @Autowired:先byType再byName;@Resource:先byName,再byType
- 扫描包之后,再对类加@Component,表明这个类被Spring管理了,是个bean,再利用@Value对属性进行注入
<!--指定要扫描的包,这个包下面的注解就会生效-->
<context:component-scan base-package="com.gx.pojo"/>
<!--开启注解支持-->
<context:annotation-config/>
//等价于<bean id="user" class="com.gx.pojo.User"/>
@Component
@Scope("singleton")
public class User {
@Value("张三")
public String name;
}
- @Component的衍生注解:Dao层@Repository,Service@Service,Controller@Controller。这三个注解都是将某个类注册到Spring中,注册为bean。只需要在xml文件中将该包扫描一下即可。
小结:
- xml更加万能,适用于任何场景,维护简单方便
- 注解维护相对复杂
- xml和注解最佳实践:xml用于管理bean,注解只负责属性的注入
2.1.10 使用Java方法配置Spring
完全不使用Spring的xml配置,全权交给Java来做
实体类
package com.gx.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//@Component将这个类托管给Spring,注册到了容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
//属性注入值
@Value("张三")
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置文件,用Java编写
package com.gx.config;
import com.gx.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan("com.gx.pojo")
@Import(MyConfig2.class)
public class MyConfig {
//注册一个bean,就相当于之前写的一个bean标签
//方法名就是bean标签里的id
//方法的返回值就是bean标签里的class属性
@Bean
public User getUser() {
return new User();
}
}
@Configuration相当于<beans>
@ComponentScan扫描包,使注解有效
@Import将两个配置类融合
测试:通过AnnotationConfigApplicationContext获取容器
@Test
public void test() {
//如果完全使用了配置类方式去做,只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("getUser", User.class);
System.out.println(user);
}
2.2 AOP理论推导
2.2.1 代理模式
学习代理模式的原因:代理模式是Spring AOP的底层
代理模式分类:
- 静态代理
- 动态代理
1、静态代理
角色分析:
抽象角色:一般用接口或抽象类
真实角色:被代理的角色
代理角色:代理真实角色,会做一些附属操作
客户:访问代理角色的人
静态代理模式的好处:可以使真实角色的操作更加纯粹,不用去关注一些公共的业务,例如日志业务直接在代理处添加即可。
缺点:一个真实角色就需要一个代理角色,代码量翻倍,开发效率变低。
package com.gx.demo01;
//租房接口
public interface Rent {
public void rent();
}
package com.gx.demo01;
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子");
}
}
package com.gx.demo01;
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
seeHouse();
host.rent();
fare();
hetong();
}
public void seeHouse() {
System.out.println("中介带你看房");
}
public void fare() {
System.out.println("收中介费");
}
public void hetong() {
System.out.println("签租赁合同");
}
}
package com.gx.demo01;
public class Client {
public static void main(String[] args) {
//房东要组房子
Host host = new Host();
//代理,中介帮房东租房子,代理角色会有一些附属操作
Proxy proxy = new Proxy(host);
//你不要面对房东,直接找中介即可
proxy.rent();
}
}
2、动态代理
角色分析:与静态代理一致
动态代理的代理类是动态生成的,不是直接写好的。
动态代理分为两大类:基于接口的动态代理和基于类的动态代理
- 基于接口--JDK动态代理【重点】
- 基于类:cglib
- Java字节码实现:Javasisst
两个类Proxy和InvocationHandler
- Proxy:代理
- InvocationHandler:调用处理程序
package com.gx.demo02;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
package com.gx.demo02;
//真实对象
public class UserServicceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
package com.gx.demo04;
import com.gx.demo03.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//这个类自动生成代理类(通用)
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy() {
//反射
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果(代理或者接口)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
//动态代理的本质:使用反射机制实现
Object result = method.invoke(target, args);
return result;
}
public void log(String msg) {
System.out.println("执行了" + msg + "方法");
}
}
(通用的动态代理类,属性是需要代理的接口。根据属性得到代理类,调用时只需要新建一个需要新建一个需要代理的对象,然后丢到该动态代理类即可。代理实现方法是根据反射进行的)
package com.gx.demo04;
import com.gx.demo02.UserServicceImpl;
import com.gx.demo02.UserService;
public class Client {
public static void main(String[] args) {
//真实角色
UserServicceImpl userServicce = new UserServicceImpl();
//代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userServicce); //设置代理对象
//动态生成代理类
UserService proxy = (UserService) pih.getProxy();
proxy.add();
}
}
动态代理的好处:
- 包含静态代理的所有好处
- 一个动态代理类代理的是一个接口,一般就是对应一类业务
- 一个动态代理可以代理多个类,只要是这个接口产生的类
- (属性是接口,使用时只需要将该接口的实现类的对象放入即可)
2.2.2 AOP
1、什么是AOP?
AOP(Aspect Oriented Programming):面向切面编程。是通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可复用性,同时提高了开发的效率。
2、AOP在Spring中的作用
- 提供声明式事务
- 允许用户自定义切面
3、Spring中AOP的实现
- 导入依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
- 方式1:使用Spring中的API接口【主要是Spring API接口实现】
<!--方式1:使用Spring API接口-->
<!--配置AOP:需要导入AOP的约束alt+enter-->
<aop:config>
<!--切入点,expression表达式,execution(要执行的位置):固定-->
<aop:pointcut id="pointcut" expression="execution(* com.gx.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
(pointcut:拦截点,可以是一个方法,advisor:通知,遇到拦截点后执行的代码,通知分为前置、后置、异常、最终、环绕通知五类)
- 方式2:自定义类实现接口
<!--方式2:自定义类-->
<bean id="diy" class="com.gx.diy.DIYPointCut"/>
<aop:config>
<!--自定义切面,ref 要引用的类-->
<aop:aspect ref="diy">
<!--切入点-->
<aop:pointcut id="point" expression="execution(* com.gx.service.UserServiceImpl.*(..))"/>
<!--通知.添加的方法-->
<aop:before method="before" pointcut-ref="point"/>
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
- 方式3:使用注解实现
<!--方式3:使用注解实现AOP-->
<bean id="annotationPointCut" class="com.gx.diy.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
2.3 整合Mybatis
1、导入相关jar包和配置静态资源导出
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<!--Spring操作数据库时,还需要spring-jdbc-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</dependency>
</dependencies>
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
2、编写实体类、接口和mapper.xml、实现类
package com.gx.pojo;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String pwd;
}
package com.gx.mapper;
import com.gx.pojo.User;
import java.util.List;
public interface UserMapper {
public List<User> selectUser();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gx.mapper.UserMapper">
<select id="selectUser" resultType="user">
select * from mybatis.user;
</select>
</mapper>
package com.gx.mapper;
import com.gx.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper{
//以前所有操作都使用sqlSession来执行,现在都使用SqlSeesionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
3、编写配置文件
-
mybatis.xml:使用spring来代替mybatis的配置,留下设置和别名。(数据库配置和注册mapper由spring完成)
<typeAliases>
<package name="com.gx.pojo"/>
</typeAliases>
- mybatis-spring.xml配置:包括数据源配置、sqlSessionFactory(产生sqlSession,sqlSession是执行sql命令的会话)和sqlSessionTemplate(sqlSession的实现类,是线程安全的)。
<!--DataSource:使用Spring的数据源替换mybatis的配置 c3p0 dbcp druid
这里使用Spring提供的JDBC
-->
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--产生sqlSessionFactory-->
<bean id="sqlSeeionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="datasource"/>
<!--绑定mybatis配置文件,对应被映射的sql语句-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:com/gx/mapper/*.xml"/>
</bean>
<!--产生sqlSession-->
<!--SqlSesionTemplate就是使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--源码可知,只能通过构造器注入sqlSessionFactory,因为没有set方法-->
<constructor-arg index="0" ref="sqlSeeionFactory"/>
</bean>
- spring配置文件(applicationContext.xml)
<import resource="spring-dao.xml"/>
<bean id="userMapper" class="com.gx.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSession"/>
</bean>
4、测试
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> userList = userMapper.selectUser();
for (User user: userList) {
System.out.println(user);
}
}
(userMapper-->sqlSession(sqlSessionTemplate)-->sqlSessionFactory-->sqlSessionFactroryBean)
5、结果
User(id=1, name=系统管理员, pwd=1234567)
User(id=5, name=韩路彪, pwd=0000000)
User(id=6, name=hi, pwd=12334)
User(id=100, name=张三, pwd=12211212)
Process finished with exit code 0
2.4 声明式事务
把一组业务当作一个业务来做,要么都成功,要么都失败(例如银行转账)。
事务ACID原则:
- 原子性
- 一致性
- 隔离性:多个业务之间相互解耦
- 持久性:事务一旦提交,无论系统发生什么问题,结果都不会受到影响
Spring中的事务管理:
- 声明式事务:AOP
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"/>
</bean>
<!--结合AOP实现事务的植入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给那些方法配置事务-->
<!--配置事务的传播特性:propagation REQUIRED默认的-->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="del" propagation="REQUIRED"/>
<tx:method name="updata" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCup" expression="execution(* com.gx.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCup"/>
</aop:config>
@Override
public List<User> selectUser() {
User user = new User(2, "张三", "12211212");
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.delUser(100);
mapper.addUser(user);
return mapper.selectUser();
}
结果:id=2的数据已经存在,那么必然添加不进去。不使用事务时,id=100的数据会被删除,而使用事务时,id=100的数据不会被删除。
- 编程式事务:需要在代码中进行事务管理
三、SpringMVC
SSM=Mybatis+Spring+SpringMVC
MVC三层架构:模型(dao service) 视图(jsp) 控制器(servlet)。是一种软件设计规范。
3.1 SpringMVC初探
SpringMVC是Spring Framework的一部分,是基于Java实现MVC的轻量级Web框架。
SpringMVC的优点:
- 轻量级,简单易学
- 高效,基于请求响应的MVC框架
- 与Spring兼容性好,无缝连接(Spring容器可以将SpringMVC中所有要用到的bean注册到Spring中)
- 约定大于配置
- 功能强大:RestFul,数据验证,格式化、本地化、主题
中心控制器:SpringMVC的web框架围绕DispacherServlet设计,DispacherServlet的作用是将请求分发给不同的处理器。
环境搭建:
1、依赖
<!--导入依赖-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
2、添加外部框架web4.0,并配置web.xml,注册DispatcherServlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--注册DispacherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--关联一个springmvc配置文件[servlet-name]-servlet.xml-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<!--启动级别-1-->
<load-on-startup>1</load-on-startup>
</servlet>
<!--/匹配所有请求,不包括.jsp-->
<!--/*匹配所有请求,包括.jsp-->
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3、编写spring-servlet.xml
使用SpringMVC必须配置的三大件:处理器映射器、处理器适配器、视图解析器
通常只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!--处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!--视图解析器:DispatcherServlet提供的ModelAndView
1、获取ModelAndView的数据
2、解析了ModelAndView的视图名字
3、拼接视图名字,找到对应的视图 /WEN-INF/jsp/hello.jsp
4、将数据渲染到这个视图
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/jsp/"/>
<!--后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--Handler-->
<bean id="/hello" class="com.gx.controller.HelloController"/>
</beans>
4、编写jsp和controller
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${msg}
</body>
</html>
package com.gx.controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
public class HelloController implements Controller {
public ModelAndView handleRequest(HttpServletRequest req, HttpServletResponse resp) {
//ModelAndView 模型和视图
ModelAndView modelAndView = new ModelAndView();
//封装对象,放在ModelAndView中,Model
modelAndView.addObject("msg","HelloSpringMVC");
//封装要跳转的视图,放在ModelAndView中,设置为/WEB-INF/jsp/hello.jsp
modelAndView.setViewName("hello");
return modelAndView;
}
}
注意:
- 报找不到类错误时,需要添加lib文件夹,并将类放入
- 运行后报错(某个路径找不到)且没有taget文件,可能是需要静态资源导出。仍然无用,手动添加:generate sources and update folder,并刷新,发现target目录出现了,但是缺少目录,修改.iml文件(删去最后几行)。运行tomcat后如果报SAXParseException异常,那么将spring配置文件的encoding改为UTF8即可
3.2 SpringMVC原理
1、DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收并拦截请求
- 请求url:http://localhost:8080/SpringMVC/hello
- 服务器域名:http://localhost:8080
- 部署在服务器上的web站点:SpringMVC
- 控制器:hello
2、HandlerMapping为处理器映射,DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler
3、HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,对于上述url,那么被查找到的控制器为hello
4、HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射信息等
5、HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler
6、Handler让具体的Controller执行
7、Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView。
8、HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet
9、DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名
10、视图解析器将解析的逻辑实体名传递给DispatcherServlet
11、DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图
12、最终视图呈现给用户
3.3 注解
每个控制器都在Spring配置文件中注册比较麻烦,对controller进行扫描,然后使用注解@Controller就不需要在Spring配置文件中注册。
<!--自动扫描包,让指定包下的注解生效,由IOC容器统一管理-->
<context:component-scan base-package="com.gx.controller"/>
<!--让springmvc不处理静态资源(过滤) .css .js .html .mp3 .mp4-->
<mvc:default-servlet-handler/>
<!--
支持mvc注解驱动:
在spring中一般使用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效,必须向上下文注册@DefaultAnnotationHandlerMapping和一个AnnotationMethodHandlerMappingAdapter实例
这两个实例分别在类级别和方法级别处理,
而annotation-driver配置帮助我们自动完成上述两个实例的注入
-->
<mvc:annotation-driven/>
package com.gx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/hello")
public class HelloController {
//localhost:8080/hello/h1
@RequestMapping("/h1")
public String hello(Model model) {
//封装数据
model.addAttribute("msg","Hello,SpringMVCAnnotation");
return "hello"; //会被视图解析器处理 /WEB-INF/jsp/hello.jsp
}
}
3.4 Controller配置总结
两种实现
- 实现Controller接口+注册bean
- 使用注解:扫描目录+注解
tomcat的几种启动:
- 修改配置文件:restart
- 修改Java代码:redeploy
- 改变jsp代码:update classes and resources
3.5 RestFul风格
Restful风格就是一个资源定位和资源操作的风格,不是标准也不是协议。基于这个风格设计的软件可以更简洁,更有层次,也更容易实现缓存等机制
原来的:http://localhost:8080/add?a=1&b=2
RestFul:http://localhost:8080/add/1/2
两种方式
- RequestMapping(value=,method=)
@RequestMapping(value = "/add/{a}/{b}",method = RequestMethod.GET)
public String test1(@PathVariable int a,@PathVariable int b, Model model) {
int res = a + b;
model.addAttribute("msg","结果为" + res);
return "test";
}
- GetMapping(value=)
@GetMapping("/add/{a}/{b}")
public String test1(@PathVariable int a,@PathVariable int b, Model model) {
int res = a + b;
model.addAttribute("msg","结果为" + res);
return "test";
}
类似的还有@PostMapping(),只是直接走/add/1/2行不通,这个默认是get。使用表单form跳转可以
<form action="/add/1/3" method="post">
<input type="submit">
</form>
3.6 结果跳转方式(重定向和转发)
- 方式1:使用视图解析器
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
默认是转发:
return "test" == return "forward:/WEB-INF/jsp/test.jsp"
使用重定向的话,不能访问/WEB-INF下的资源,因为重定向本质是重新请求一个新地址,是一个新的请求。而且这个请求是在客户端上完成的,只有服务端的请求才能访问/WEB-INF下的请求。所以重定向使用不了视图解析器。
return "redirect:/index.jsp"
- 方式2:使用ServletAPI
通过设置ServletAPI(HttpServletResponse),不需要视图解析器
输出:resp.getWriter().println()
重定向:resp.sendRedirect()
转发:resp.getRequestDispatcher().forword()
3.7 SpringMVC数据处理
3.8.1 处理提交数据
1、提交的域名称与处理方法的参数名一致
@GetMapping("/t1")
public String test1(String name, Model model) {
//接收前端参数
System.out.println("接收到前端的参数为:" + name);
//将返回的结果传递给前端
model.addAttribute("msg",name);
//视图跳转
return "test";
}
2、提交的域名称和处理方法的参数名不一致:使用注解。
使用注解就指定了前端的域名称,不符合不跳转(省资源)
@GetMapping("/t1")
//对前端端传来的参数使用注解,名称不一致,不会跳转
public String test1(@RequestParam("username") String name, Model model) {
//接收前端参数
System.out.println("接收到前端的参数为:" + name);
//将返回的结果传递给前端
model.addAttribute("msg",name);
//视图跳转
return "test";
}
3、提交的是一个对象:只要保证提交的表单域和对象的属性名一致,那么参数使用对象即可
@GetMapping("/t2")
public String test2(User user) {
System.out.println(user);
return "test";
}
3.7.2 数据显示到前端
- 方式1:ModelAndView
前面已提,实现controller接口,还需要配置springmvc文件(注册)
- 方式2:Model
前面已提,使用注解,更简单
- 方式3:ModelMap
@GetMapping("/t3")
public String test3(@RequestParam("username") String name,ModelMap map) {
map.addAttribute("msg",name);
System.out.println(name);
return "test";
}
3.8 乱码问题
3.8.1 手写过滤器
以下form表单,提交name为中文时,网页会乱码。
<form action="/e/t1" method="post">
<input type="text" name="name">
<input type="submit">
</form>
通过手写过滤器解决网页乱码问题
package com.gx.filter;
import javax.servlet.*;
import java.io.IOException;
public class EncodingFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
servletRequest.setCharacterEncoding("utf-8");
servletResponse.setCharacterEncoding("utf-8");
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {
}
}
注册
<!--手写过滤器-->
<filter>
<filter-name>encodingfilter</filter-name>
<filter-class>com.gx.filter.EncodingFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>encodingfilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
结果
3.9.2 SpringMVC自带过滤器
直接注册
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
结果
3.9 JSON
前后端分离时代:
后端提供接口,提供数据
JSON
前端负责渲染后端的数据
3.9.1 什么是JSON
- JSON(JavaScript Object Notation,JS对象标记)是一种轻量级的数据交换格式。
- 采用完全独立于编程语言的文本格式来存储和表示数据
- 简洁和清晰的层次结构使得JSON成为理想的数据交换语言。
- 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率
JSON的语法格式:
- 对象表示为键值对,数据由逗号分隔
- 花括号保存对象
- 方括号保存数组
2.9.2 SpringMVC解析json:jackson、fastjson
1、jackson
导入依赖jackson
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
未配置RequestMapping(),会乱码
配置RequestMapping(),正常显示
@RequestMapping(value = "/j1",produces = "application/json;charset=utf-8")
@ResponseBody
public String json1() throws JsonProcessingException {
//jackson:ObjectMapper
ObjectMapper mapper = new ObjectMapper();
User user = new User("张三", 20,"男");
String s = mapper.writeValueAsString(user);
return s;
}
但是这种方式比较麻烦,每个方法都需要配置RequestMapping(),一劳永逸的方法是在SpringMVC配置文件配置
<!--JSON乱码问题配置-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="utf-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
- @RestController和@ResponseBody
@ResponseBody是对方法注解的,使得这个方法返回的字符串直接显示在网页上,而不会以视图解析器的形式跳转页面
@RestController是对类的下的所有方法返回的字符串直接显示在网页上,而不会以视图解析器的形式跳转页面
2、fastjson
fastjson是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象和JavaBean对象的转换以及JavaBean对象和json对象的转换。
导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
//fastjson
@RequestMapping("/j6")
public String json4() {
ArrayList<User> users = new ArrayList<User>();
for (int i = 0; i < 5; i++) {
users.add(new User("李四"+i+"号",16+i,"女"));
}
return JSON.toJSONString(users);
}
Java对象转JSON字符串:JSON.toJSONString()
JSON字符串转Java对象:JSON.parseObject(str,User.class)
Java对象转JSON对象:JSON.toJavaObject(user)
JSON对象转Java对象:JSON.toJavaObject(jsonObject,User.class)
3.10 AJAX技术
3.10.1 AJAX简介
AJAX(Asynchronous JavaScript and XML):异步的JavaScript和XML。
AJAX是一种在无需重新加载整个页面的情况下,能够更新部分网页的技术(例如,百度搜索框)
AJAX不是一种新的编程语言,而是一种用于创建更好更快以及交互性更强的Web应用程序技术。
使用AJAX可以做:
- 注册时,输入用户名自动检测用户是否已经存在
- 登录时,提示用户名密码错误
- 删除数据行时,将行ID发送到后台,后台在数据库中删除,数据库删除成功后,在页面DOM中也将数据行删除
- ...
AJAX的核心是XMLHttpRequest对象(XHR)。XHR为向服务器发送请求和解析服务器响应提供了接口,能够以异步方式从服务器获取新数据
JQuery 提供多个与AJAX相关的方法。通过jQuery AJAX方法,能够使用HTTP Get和HTTP Post从远程服务器上请求文本、HTML、XML、JSON,同时也能够将这些外部数据直接载入网页的被选元素上。
jQuery.ajax(...)
部分参数:
url:请求地址
type:请求方式,GET、POST(1.9.0之后用method)
headers:请求头
data:要发送的数据
contentType:即将发送信息至服务器的内容编码类型(默认: "application/x-www-form-urlencoded; charset=UTF-8")
async:是否异步
timeout:设置请求超时时间(毫秒)
beforeSend:发送请求前执行的函数(全局)
complete:完成之后执行的回调函数(全局)
success:成功之后执行的回调函数(全局)
error:失败之后执行的回调函数(全局)
accepts:通过请求头发送给服务器,告诉服务器当前客户端可接受的数据类型
dataType:将服务器端返回的数据转换成指定类型
"xml": 将服务器端返回的内容转换成xml格式
"text": 将服务器端返回的内容转换成普通文本格式
"html": 将服务器端返回的内容转换成普通文本格式,在插入DOM中时,如果包含JavaScript标签,则会尝试去执行。
"script": 尝试将返回值当作JavaScript去执行,然后再将服务器端返回的内容转换成普通文本格式
"json": 将服务器端返回的内容转换成相应的JavaScript对象
"jsonp": JSONP 格式使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数
3.10.2 环境配置
使用原始的HtppServletResponse测试:
1、配置web.xml和springmvc配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动扫描指定的包,下面所有注解类交给IOC容器管理 -->
<context:component-scan base-package="com.gx.controller"/>
<!--静态资源过滤-->
<mvc:default-servlet-handler />
<!--JSON乱码问题配置-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="utf-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
<!--拦截器配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--/**包括这个请求下所有的请求-->
<mvc:mapping path="/user/**"/>
<bean class="com.gx.config.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
2、controller层
@RequestMapping("/a1")
public void a1(String name, HttpServletResponse response) throws IOException {
System.out.println("a1:param:" + name);
if("gx".equals(name)) {
response.getWriter().print("true");
} else {
response.getWriter().print("false");
}
}
3、导入jquery.js
官网下载好后存入WEB-INF目录下
<script src="${pageContext.request.contextPath}/statics/js/jquery-3.6.0.js"></script>
4、编写index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
<script src="${pageContext.request.contextPath}/statics/js/jquery-3.6.0.js"></script>
<script>
function a() {
$.post({
url:"${pageContext.request.contextPath}/a1",
/*data键:是后端的参数名,值是前端属性id,属性名前面加# */
data:{"name":$("#username").val()},
success:function (data,status) {
console.log("data:" + data);
console.log("status:" + status); //200 300 400 500
}
});
}
</script>
</head>
<body>
<%--失去一个焦点的时候,发起一个请求到后台--%>
用户名<input type="text" id="username" onblur="a()">
</body>
</html>
3.10.3 SpringMVC实现
1、实体类
package com.gx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String name;
private int age;
private String gender;
}
2、controller
@RequestMapping("/a2")
public List<User> a2() {
ArrayList<User> users = new ArrayList<User>();
for (int i = 0; i < 3; i++) {
users.add(new User("狂神说"+(i==0?"Java":(i==1?"前端":"运维")),i+18,i%2==0?"男":"女"));
}
return users;
}
3、前端test2.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="${pageContext.request.contextPath}/statics/js/jquery-3.6.0.js"></script>
<script>
$(function () {
$("#btn").click(function () {
/*
* $.post(url,param[可省略],success)
* */
$.post("${pageContext.request.contextPath}/a2",function (data) {
// console.log(data);
var html = "";
for (let i = 0; i < data.length; i++) {
html += "<tr>" +
"<td>" + data[i].name + "</td>" +
"<td>" + data[i].age + "</td>" +
"<td>" + data[i].gender + "</td>" +
"</tr>"
}
$("#content").html(html);
});
})
})
</script>
</head>
<body>
<input type="button" value="加载数据" id="btn">
<table>
<tr>
<td>姓名</td>
<td>年龄</td>
<td>性别</td>
</tr>
<tbody id="content">
</tbody>
</table>
</body>
</html>
3.10.4 注册提示效果
@RequestMapping("/a3")
public String a3(String name,String pwd) {
String msg = "";
if (name != null) {
if("admin".equals(name)) {
msg = "OK";
} else {
msg = "用户名有误";
}
}
if (pwd != null) {
if("123456".equals(pwd)) {
msg = "OK";
} else {
msg = "密码有误";
}
}
return msg;
}
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="${pageContext.request.contextPath}/statics/js/jquery-3.6.0.js"></script>
<script>
function a1() {
$.post("${pageContext.request.contextPath}/a3",{"name":$("#name").val()},function (data) {
/*console.log()在开发者模式中查看,alert()直接弹窗,中文字符乱码的话,就配置JSON乱码配置*/
// console.log(data);
if (data.toString() === 'OK') {
$("#userInfo").css("color","green");
} else {
$("#userInfo").css("color","red");
}
$("#userInfo").html(data);
}) ;
}
function a2() {
$.post("${pageContext.request.contextPath}/a3",{"pwd":$("#pwd").val()},function (data) {
// console.log(data);
if (data.toString() === 'OK') {
$("#pwdInfo").css("color","green");
} else {
$("#pwdInfo").css("color","red");
}
$("#pwdInfo").html(data);
}) ;
}
</script>
</head>
<body>
<p>
用户名:<input type="text" id="name" onblur="a1()">
<span id="userInfo"></span>
</p>
<p>
密码:<input type="password" id="pwd" onblur="a2()">
<span id="pwdInfo"></span>
</p>
</body>
</html>
主要处理JSON乱码问题
<!--JSON乱码问题配置-->
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<constructor-arg value="utf-8"/>
</bean>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="failOnEmptyBeans" value="false"/>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
3.11 拦截器
SpringMVC的处理拦截器类似于Servlet开发中的过滤器Filter(例如乱码过滤器),用于对处理器进行预处理和后处理。
过滤器和拦截器的区别:拦截器是AOP思想的具体应用。
过滤器:
- Servlet规范中的一部分,任何JavaWeb工程都可以使用
- 在url-pattern中配置/*之后,可以对所有要访问的资源进行拦截
拦截器:
- 拦截器是SpringMVC框架下的,只有使用了SpringMVC框架的工程才能使用
- 拦截器只会拦截访问的控制器方法,如果访问都得是jsp/html/css/image/js是不会进行拦截的
自定义拦截器:实现HandlerIntercepter接口
拦截器应用:登录判断验证(验证用户是否验证,若已经登录,放行;否则跳转登录页面
1、springmvc配置文件
<!--拦截器配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--/**包括这个请求下所有的请求-->
<mvc:mapping path="/user/**"/>
<bean class="com.gx.config.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
2、拦截器
package com.gx.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
//放行:判断什么情况下登录
//登录页面放行
if (request.getRequestURL().toString().contains("Login")) { //"/goLogin"
return true;
}
if (request.getRequestURL().toString().contains("login")) {
return true;
}
//登录了之后放行
if (session.getAttribute("userLoginInfo") != null) {
return true;
}
//其它不放行,转发到登录页面
request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
return false;
}
}
- 路径中包含"Login",放行:前往登录页面
- 路径中包含"login",放行:登录页面
- session的userLoginInfo不为空,放行:已登录
- 其它不放行,跳转到登录页面
3、前端login.jsp、main.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--载web-inf下的所有页面或者资源,只能通过controller或者servlet进行访问--%>
<h1>登录页面</h1>
<form action="${pageContext.request.contextPath}/user/login" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--往往登录之后才能访问,使用拦截器使得登陆后才能进入该页面--%>
<h1>首页</h1>
<span>${username}</span>
<p><a href="${pageContext.request.contextPath}/user/goOut">注销</a></p>
</body>
</html>
4、控制器
package com.gx.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("/user")
public class LoginController {
@RequestMapping("/login")
public String login(HttpSession session, String username, String password, Model model) {
//把用户的信息存在session中
model.addAttribute("username",username);
session.setAttribute("userLoginInfo",username);
return "main";
}
//这是首页,设置拦截器,使得没登陆无法进入首页
@RequestMapping("/main")
public String main() {
return "main";
}
@RequestMapping("/goLogin")
public String login() {
return "login";
}
@RequestMapping("/goOut")
public String goOut(HttpSession session) {
session.removeAttribute("userLoginInfo");
return "login";
}
}
前面拦截器已经配置,无法直接通过/user/main进入首页,会跳转到login.jsp。只有登录成功之后,才能进入首页。
3.12 文件上传和下载
SprinvMVC可以很好的支持文件上传操作,但是需要在上下文配置MultiResolver。
前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才能将用户选的文件以二进制数据发送给服务器。
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload" enctype="multipart/form-data" method="post">
<input type="file" name="file">
<input type="submit" value="upload">
</form>
</html>
一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。
2.13.1文件上传
导入依赖
<dependencies>
<!--文件上传-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
<!--servlet-api导入高版本的-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
配置bean:multipartResolver
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>
Controller
//@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
//批量上传CommonsMultipartFile则为数组即可
@RequestMapping("/upload")
public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
//获取文件名 : file.getOriginalFilename();
String uploadFileName = file.getOriginalFilename();
//如果文件名为空,直接回到首页!
if ("".equals(uploadFileName)){
return "redirect:/index.jsp";
}
System.out.println("上传文件名 : "+uploadFileName);
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
//如果路径不存在,创建一个
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
System.out.println("上传文件保存地址:"+realPath);
InputStream is = file.getInputStream(); //文件输入流
OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流
//读取写出
int len=0;
byte[] buffer = new byte[1024];
while ((len=is.read(buffer))!=-1){
os.write(buffer,0,len);
os.flush();
}
os.close();
is.close();
return "redirect:/index.jsp";
}
文件上传到out目录下。
2、采用file.Transto来保存上传的文件
@RequestMapping("/upload2")
public String fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
//上传路径保存设置
String path = request.getServletContext().getRealPath("/upload");
File realPath = new File(path);
if (!realPath.exists()){
realPath.mkdir();
}
//上传文件地址
System.out.println("上传文件保存地址:"+realPath);
//通过CommonsMultipartFile的方法直接写文件(注意这个时候)
file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
return "redirect:/index.jsp";
}
修改前端表单提交地址
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/upload2" enctype="multipart/form-data" method="post">
<input type="file" name="file">
<input type="submit" value="upload">
</form>
</html>
3.12.2 文件下载
文件下载步骤:
- 设置response响应头
- 读取文件--InputStream
- 写出文件--OutputStream
- 执行操作
- 关闭流
//下载图片
@RequestMapping(value="/download")
public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
//要下载的图片地址
String path = request.getServletContext().getRealPath("/upload");
String fileName = "基础语法.jpg";
//1、设置response 响应头
response.reset(); //设置页面不缓存,清空buffer
response.setCharacterEncoding("UTF-8"); //字符编码
response.setContentType("multipart/form-data"); //二进制传输数据
//设置响应头
response.setHeader("Content-Disposition",
"attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
File file = new File(path,fileName);
//2、 读取文件--输入流
InputStream input=new FileInputStream(file);
//3、 写出文件--输出流
OutputStream out = response.getOutputStream();
byte[] buff =new byte[1024];
int index=0;
//4、执行 写出操作
while((index= input.read(buff))!= -1){
out.write(buff, 0, index);
out.flush();
}
out.close();
input.close();
return null;
}
前端
<a href="${pageContext.request.contextPath}/download">点击下载</a>
小结:上传是从本地上传到服务器中,下载是从服务器上下载到本地。
四、项目整合
依赖和静态资源导出
<?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>com.gx</groupId>
<artifactId>ssmbuild</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>books</module>
</modules>
<!--依赖 junit 数据库驱动 连接池 servlet jsp mybatis mabatis-spring spring-->
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.24</version>
</dependency>
<!--数据库连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
</dependencies>
<!--静态资源导出-->
<!--在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.1 mybatis层整合
资源:mybatis配置文件:连接数据库,交给spring(5.7以上版本数据库驱动器是com.mysql.cj.jdbc.Driver)
pojo与数据库表相对应
dao层:增删改查接口和相应配置文件。配置文件写完后要注册到mybatis配置文件中。(mapper)
service层(业务层):调用dao层的方法即可,接口加相应实现类。实现类:调用dao层(属性+相应set方法)
<!--日志-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--配置数据源,交给spring去做-->
<typeAliases>
<package name="com.gx.pojo"/>
</typeAliases>
<mappers>
<mapper class="com.gx.dao.BookMapper"/>
</mappers>
package com.gx.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Books {
private int bookID;
private String bookName;
private int bookCounts;
private String detail;
}
package com.gx.dao;
import com.gx.pojo.Books;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface BookMapper {
int addBooks(Books books);
int deleteBookById(@Param("bookId") int id);
int updateBook(Books books);
Books queryBookById(@Param(("bookId")) int id);
List<Books> queryAllBook();
Books qureyBookByName(@Param("bookName") String bookName);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.gx.dao.BookMapper">
<insert id="addBooks" parameterType="Books">
insert into ssmbuild.books (bookName, bookCounts, detail) VALUES (#{bookName},#{bookCounts},#{detail});
</insert>
<delete id="deleteBookById" parameterType="int">
delete from ssmbuild.books where bookID = #{bookId};
</delete>
<update id="updateBook" parameterType="Books">
update ssmbuild.books
set bookName = #{bookName},bookCounts = #{bookCounts},detail = #{detail}
where bookID = #{bookID};
</update>
<select id="queryBookById" resultType="Books">
select * from ssmbuild.books
where bookID = #{bookId};
</select>
<select id="queryAllBook" resultType="Books">
select * from ssmbuild.books;
</select>
<select id="qureyBookByName" resultType="Books">
select * from ssmbuild.books where bookName = #{bookName};
</select>
</mapper>
package com.gx.service;
import com.gx.pojo.Books;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface BookService {
int addBooks(Books books);
int deleteBookById(int id);
int updateBook(Books books);
Books queryBookById(int id);
List<Books> queryAllBook();
Books qureyBookByName(String bookName);
}
package com.gx.service;
import com.gx.dao.BookMapper;
import com.gx.pojo.Books;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookMapper bookMapper;
public void setBookMapper(BookMapper bookMapper) {
this.bookMapper = bookMapper;
}
@Override
public int addBooks(Books books) {
return bookMapper.addBooks(books);
}
@Override
public int deleteBookById(int id) {
return bookMapper.deleteBookById(id);
}
@Override
public int updateBook(Books books) {
System.out.println("[service]" + books);
return bookMapper.updateBook(books);
}
@Override
public Books queryBookById(int id) {
return bookMapper.queryBookById(id);
}
@Override
public List<Books> queryAllBook() {
return bookMapper.queryAllBook();
}
@Override
public Books qureyBookByName(String bookName) {
return bookMapper.qureyBookByName(bookName);
}
}
4.2 Spring层整合
spring-dao.xml:关联数据库配置文件、连接池、sqlSessionFactory、dao接口扫描包(实现dao接口注入到spring容器中,从而免去了写daomapper的实现类)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--1、关联数据库配置文件-->
<context:property-placeholder location="classpath:database.properties"/>
<!--2、连接池
dbcp:半自动化操作,不能自动连接
c3p0:自动化操作,自动化加载配置文件,可以自动设置到对象中
druid:
hikari
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<!--c3p0私有属性-->
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<!--关闭连接池后不自动commit-->
<property name="autoCommitOnClose" value="false"/>
<!--获取连接超时时间-->
<property name="checkoutTimeout" value="10000"/>
<!--获取连接失败后重试次数-->
<property name="acquireRetryAttempts" value="2"/>
</bean>
<!--3、sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--绑定mybatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
</bean>
<!--配置dao接口扫描包,动态实现dao接口可以注入到spring容器中-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--注入sqlSessionFactory-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!--要扫描的dao包-->
<property name="basePackage" value="com.gx.dao"/>
</bean>
</beans>
database.properties
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/ssmbuild?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
jdbc.username = root
jdbc.password = 123456
spring-service.xml:扫描service下的包、将所有业务注入到spring,通过配置或者注解实现、声明式事务配置。AOP事务支持
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--扫描service下的包-->
<context:component-scan base-package="com.gx.service"/>
<!--将所有业务类注入到spring,通过配置或者注解实现-->
<bean id="BookServiceImpl" class="com.gx.service.BookServiceImpl">
<property name="bookMapper" ref="bookMapper"/>
</bean>
<!--声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--aop事务支持-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.gx.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
4.3 SpringMVC整合
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--DispatcherServlet-->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--乱码过滤-->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--Session过期时间-->
<session-config>
<session-timeout>15</session-timeout>
</session-config>
</web-app>
spring-mvc.xml:注解驱动、静态资源过滤、扫描包、视图解析器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--注解驱动-->
<mvc:annotation-driven/>
<!--静态资源过滤-->
<mvc:default-servlet-handler/>
<!--扫描包-->
<context:component-scan base-package="com.gx.controller"/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="classpath:spring-dao.xml"/>
<import resource="classpath:spring-service.xml"/>
<import resource="classpath:spring-mvc.xml"/>
</beans>
4.4 业务和测试
package com.gx.controller;
import com.gx.pojo.Books;
import com.gx.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.ArrayList;
import java.util.List;
@Controller
@RequestMapping("/book")
public class BookController {
//controller调用service层
@Autowired
@Qualifier("BookServiceImpl")
private BookService bookService;
//查询全部书籍,并返回到书籍展示页面
@RequestMapping("/allBook")
public String list(Model model) {
List<Books> books = bookService.queryAllBook();
model.addAttribute("list",books);
return "allBook";
}
//跳转到增加书籍页面
@RequestMapping("/toAddBook")
public String toAddPaper() {
return "addBook";
}
//添加书籍的请求
@RequestMapping("/addBook")
public String addBooks(Books books) {
System.out.println(books);
bookService.addBooks(books);
return "redirect:/book/allBook";
}
//跳转到修改页面
@RequestMapping("/toUpdate")
public String toUpdatePaper(int id,Model model) {
Books books = bookService.queryBookById(id);
model.addAttribute("QBook",books);
return "updateBook";
}
//修改书籍
@RequestMapping("/updateBook")
public String updateBook(Books books) {
System.out.println(books);
bookService.updateBook(books);
return "redirect:/book/allBook";
}
//删除数据
@RequestMapping("/deleteBook/{bookID}")
public String deleteBook(@PathVariable("bookID") int id) {
bookService.deleteBookById(id);
return "redirect:/book/allBook";
}
//查询书籍
@RequestMapping("/queryBook")
public String qureyBook(String queryBookName,Model model) {
Books books = bookService.qureyBookByName(queryBookName);
List<Books> list;
if(books == null) {
list = bookService.queryAllBook();
model.addAttribute("error","未查到");
} else {
list = new ArrayList<Books>();
list.add(books);
}
model.addAttribute("list",list);
return "allBook";
}
}
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookServiceImpl bookServiceImpl = context.getBean("BookServiceImpl", BookServiceImpl.class);
for (Books books : bookServiceImpl.queryAllBook()) {
System.out.println(books);
}
}
4.5 附录
前端index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
<style>
a{
text-decoration: none;
color: black;
font-size: 18px;
}
h3{
width: 180px;
height: 38px;
margin: 100px auto;
text-align: center;
line-height: 38px;
background: deepskyblue;
border-radius: 5px;
}
</style>
</head>
<body>
<h3>
<a href="${pageContext.request.contextPath}/book/allBook">进入书籍页面</a>
</h3>
</body>
</html>
add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>新增书籍</small>
</h1>
</div>
</div>
</div>
<form action="${pageContext.request.contextPath}/book/addBook" method="post">
<div class="form-group">
<label>书籍名称:</label>
<input type="text" name="bookName" class="form-control" required>
</div>
<div class="form-group">
<label>书籍数量:</label>
<input type="text" name="bookCounts" class="form-control" required>
</div>
<div class="form-group">
<label>书籍描述:</label>
<input type="text" name="detail" class="form-control" required>
</div>
<div class="form-group">
<input type="submit" class="form-control" value="添加">
</div>
</form>
</div>
</body>
</html>
add.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>书籍展示页面</title>
<%--BookStrap美化界面--%>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>书籍列表——————显示所有书籍</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-md-4 column">
<a class="btn btn-primary" href="${pageContext.request.contextPath}/book/toAddBook">新增书籍</a>
<a class="btn btn-primary" href="${pageContext.request.contextPath}/book/allBook">显示全部书籍</a>
</div>
<div class="col-md-4 column"></div>
<div class="col-md-4 column">
<%--查询书籍--%>
<form class="form-inline" action="${pageContext.request.contextPath}/book/queryBook" method="post" style="float: right">
<span style="color: red;font-weight: bold">${error}</span>
<input type="text" name="queryBookName" class="form-control" placeholder="请输入要查询的书籍名称">
<input type="submit" value="查询" class="btn btn-primary">
</form>
</div>
</div>
</div>
<div class="row clearfix">
<div class="col-md-12 column">
<table class="table table-hover table-striped">
<thead>
<tr>
<th>书籍编号</th>
<th>书籍名称</th>
<th>书籍数量</th>
<th>书籍详情</th>
<th>操作</th>
</tr>
</thead>
<%--数据从数据库中查询,从books中遍历出来--%>
<tbody>
<c:forEach var="book" items="${list}">
<tr>
<td>${book.bookID}</td>
<td>${book.bookName}</td>
<td>${book.bookCounts}</td>
<td>${book.detail}</td>
<td>
<a href="${pageContext.request.contextPath}/book/toUpdate?id=${book.bookID}">修改</a>
|
<a href="${pageContext.request.contextPath}/book/deleteBook/${book.bookID}">删除</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
</div>
</div>
</div>
</body>
</html>
update.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>修改书籍</title>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row clearfix">
<div class="col-md-12 column">
<div class="page-header">
<h1>
<small>书籍修改</small>
</h1>
</div>
</div>
</div>
<form action="${pageContext.request.contextPath}/book/updateBook" method="post">
<%--隐藏域--%>
<input type="hidden" name="bookID" value="${QBook.bookID}">
<div class="form-group">
<label>书籍名称:</label>
<input type="text" name="bookName" class="form-control" value="${QBook.bookName}" required>
</div>
<div class="form-group">
<label>书籍数量:</label>
<input type="text" name="bookCounts" class="form-control" value="${QBook.bookCounts}" required>
</div>
<div class="form-group">
<label>书籍描述:</label>
<input type="text" name="detail" class="form-control" value="${QBook.detail}" required>
</div>
<div class="form-group">
<input type="submit" class="form-control" value="修改">
</div>
</form>
</div>
</body>
</html>