ssm框架学习
1、什么是框架
它是我们软件开发中的一套解决方案,不同的框架解决的是不同的问题。
使用框架的好处:
框架封装了很多的细节,使开发者可以使用极简的方式实现功能,大大提高开发效率
2、三层架构
表现层:用于展示数据
业务层:处理业务需求
持久层:和数据库交互
3、持久层技术解决方案
JDBC技术:Connection
PreparedStatement
ResultSet
Spring的JdbcTemplate:
Spring中对jdbc的简单封装
Apache的DBUtils:
它和Spring的JdbcTemplate很想,也是对Jdbc的简单封装
以上这些都不是框架
JDBC是规范
Spring的JdbcTemplate和Apache的DBUtils都只是工具类
第一章 Mybatis
1.1、MyBatis框架概述
mybatis是一个优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动,创建连接,创建statement等繁杂的过程
mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终的sql语句,最后用mybatis框架执行sql并将结果映射为java对象并返回
采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我 们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。
ORM:Object Relational Mapping 对象关系映射
就是把数据库表和实体类及实体类的属性对应起来,让我们可以操作实体类就实现操作数据库表
1.2、mybatis入门
mybatis的环境搭建
第一步:
创建maven工程—>导入jar包依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
第二步:
创建实体类和dao的接口
第三步:
创建mybatis的主配置文件
SqlMapConfig.xml
<?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">
第四步:
创建映射配置文件
IUserDao.xml
<?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">
环境搭建的注意事项:
-
创建IUserDao.xml 和 IUserDao.java 时名称是为了和我们之前的知识保持一致
在mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
所以:IUserDao 和 IUserMapper是一样的
-
在idea中创建目录的时候,它和包是不一样的
包在创建时:com.ywb.do它是三级结构
目录在创建时:com.ywb.dao是一级目录
-
mybatis的映射配置文件位置必须和dao接口的包结构相同
-
映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
-
映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
当我们遵从了第3,4,5点之后,我们在开发中就无需再写dao的实现类
mybatis的入门案例
import com.ywb.dao.IUserDao;
import com.ywb.domain.User;
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.InputStream;
import java.util.List;
/**
* @author yang
* Mybatis的入门案例
*/
public class MybatisTest {
public static void main(String[] args) throws Exception {
//1.读取配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
}
IUserDao.xml
<?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">
<mapper namespace="com.ywb.dao.IUserDao">
<!--配置查询所有-->
<select id="findAll" resultType="com.ywb.domain.User">
select * from user
</select>
</mapper>
注意事项:
不要忘记在映射配置中告知mybatis要封装到哪个实体类中
配置的方式:指定实体类的全限定类名
入门案例的设计模式分析
明确:我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。
不管使用xml还是注释配置配置。
但是Mybatis它是支持写dao实现类的。
1.3、自定义Mybatis
- 分析
- mybatis在使用代理dao的方式实现增删改查时做的两件事
- 创建代理对象
- 在代理对象中调用selectList
- mybatis在使用代理dao的方式实现增删改查时做的两件事
自定义Mybatis能通过入门案例看到的类
class Resources
class SqlSessionFactoryBuilder
interface SqlSessionFactory
interface SqlSession
代码:https://gitee.com/yanglog/custom-mybatis.git
1.4、使用注解配置
SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--mybatis的主配置文件-->
<configuration>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql环境 -->
<environment id="mysql">
<!-- 配置事务类型 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源(连接池) -->
<dataSource type="POOLED">
<!-- 配置数据库的4个基本信息 -->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
<mappers>
<!--<mapper resource="com/ywb/dao/IUserDao.xml"/>-->
<mapper class="com.ywb.dao.IUserDao"/>
</mappers>
</configuration>
/**
* @author yang
* 查询的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
/**
* 配置sql语句
* @return
*/
String value();
}
/**
* 用户的持久层接口
*
* @author yang
*/
public interface IUserDao {
/**
* 查询所有用户的操作
* @return
*/
@Select("select * from user")
List<User> findAll();
}
1.5、Mybatis的CRUD操作
IUserDao接口
/**
* @author yang
* @date 2021年07月18日 14:42
* 用户的持久层接口
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
/**
* 保存用户操作
* @param user
*/
void saveUser(User user);
/**
* 更新用户操作
* @param user
*/
void updateUser(User user);
/**
* 删除用户操作
* @param userId
*/
void deleteUser(Integer userId);
/**
* 根据userId查询用户
* @param userId
* @return
*/
User findById(Integer userId);
/**
* 根据用户名字查询用户
* @param username
* @return
*/
List<User> findByName(String username);
/**
* 查询总记录条数
* @return
*/
int findTotal();
}
IUserDao.xml
<?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">
<mapper namespace="com.ywb.dao.IUserDao">
<!--查询所有-->
<select id="findAll" resultType="com.ywb.domain.User">
select * from user;
</select>
<!--插入用户-->
<insert id="saveUser" parameterType="com.ywb.domain.User">
insert into user (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address});
</insert>
<!--更新用户-->
<update id="updateUser" parameterType="com.ywb.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id};
</update>
<!--删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{uid};
</delete>
<!--根据用户id查询用户-->
<select id="findById" parameterType="java.lang.Integer" resultType="com.ywb.domain.User">
select * from user where id = #{uid};
</select>
<!--根据名称模糊查询-->
<select id="findByName" parameterType="java.lang.String" resultType="com.ywb.domain.User">
<!--select * from user where username like #{name};-->
select * from user where username like '%${value}%' ;
</select>
<!--查询用户的总记录条数-->
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
</mapper>
测试函数:
/**
* @author yang
* @date 2021年07月18日 17:35
*
* 测试mybatis的CRUD操作
*/
public class MybatisTest {
private InputStream in;
private SqlSession sqlSession;
private IUserDao userDao;
@Before
public void init() throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession
sqlSession = factory.openSession();
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
@After
public void destroy() throws Exception{
//提交事务
sqlSession.commit();
//6.释放资源
sqlSession.close();
in.close();
}
@Test
public void testFindAll(){
//5.执行查询方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testSave(){
User user = new User();
user.setUsername("testMybatis");
user.setAddress("广东汕尾");
user.setSex("男");
user.setBirthday(new Date());
//5.执行查询方法
userDao.saveUser(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setUsername("updateMybatis");
user.setAddress("广东广州");
user.setSex("女");
user.setBirthday(new Date());
user.setId(52);
//5.执行查询方法
userDao.updateUser(user);
}
@Test
public void testDelete(){
//执行删除方法
userDao.deleteUser(53);
}
@Test
public void testFindOne(){
User user = userDao.findById(100);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> users = userDao.findByName("王");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testFindTotal(){
int total = userDao.findTotal();
System.out.println(total);
}
}
注意:默认取消自动提交,需要手动commit
模糊查询两种写法的区别:
保存操作的细节——获取保存数据的id
<!--插入用户-->
<insert id="saveUser" parameterType="com.ywb.domain.User">
<!--配置插入后,获取插入数据的id-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address});
</insert>
@Test
public void testSave(){
User user = new User();
user.setUsername("testMybatis last insert");
user.setAddress("广东");
user.setSex("男");
user.setBirthday(new Date());
System.out.println("插入数据库前的User:"+user);
//5.执行查询方法
userDao.saveUser(user);
System.out.println("插入数据库后的User:"+user);
}
/*
输出结果:
插入数据库前的User:User{id=null, username='testMybatis last insert', address='广东', sex='男', birthday=Mon Jul 19 17:11:56 CST 2021}
插入数据库后的User:User{id=54, username='testMybatis last insert', address='广东', sex='男', birthday=Mon Jul 19 17:11:56 CST 2021}
*/
参数的深入——使用实体类的包装对象作为查询条件
parameterType(输入类型)
-
传递简单类型
-
传递pojo对象
- Mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
OGNL表达式
Object Graphic Navigation Language
对象 图 导航 语言
它是通过对象的取值方法来获取数据。在写法上把get给省略了
比如:我们获取用户的名称
类中的写法:user.getUsername();
OGNL表达式写法:user.username
mybatis中为什么能直接写username,而不用user.呢
因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
-
传递pojo包装对象
开发中通过pojo传递查询条件,传递条件是综合的查询条件,不仅包括用户查询条件还包括其他的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
pojo类中包含pojo
需求:根据用户名查询用户信息,查询条件放到Query Vo的user属性中
/**
* @author yang
* @date 2021年07月19日 19:00
*/
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
<!--根据queryVo的条件查询用户-->
<select id="findByVo" parameterType="com.ywb.domain.QueryVo" resultType="com.ywb.domain.User">
select * from user where username like #{user.username};
</select>
@Test
public void testFindByVo(){
QueryVo queryVo = new QueryVo();
User user = new User();
user.setUsername("%王%");
queryVo.setUser(user);
List<User> users = userDao.findByVo(queryVo);
for (User user1 : users) {
System.out.println(user1);
}
}
resultMap类型
resultType可以指定 pojo 将查询结果映射为 pojo,但需要 pojo 的属性名和 sql 查询的列名一致才可以映射成功
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作为一个对应关系,resultMap实质上还需要将查询结果映射到pojo对象中
resultMap可以实现将查询结果映射为复杂类型的pojo
当类中的属性名和数据库的字段名不一样时:
第一种:使用对应关系
第二种:改写sql语句
1.6、自实现dao实现类的方式
创建UserDaoImpl实现类继承IUserDao接口
import com.ywb.dao.IUserDao;
import com.ywb.domain.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
/**
* @author yang
* @date 2021年07月19日 19:49
*/
public class UserDaoImpl implements IUserDao {
SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory){
this.factory = factory;
}
public List<User> findAll() {
//1.使用SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用sqlSession中的方法,实现查询列表
List<User> users = sqlSession.selectList("com.ywb.dao.IUserDao.findAll");
//3.释放资源
sqlSession.close();
return users;
}
public void saveUser(User user) {
//1.使用SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用sqlSession中的方法,实现查询列表
sqlSession.insert("com.ywb.dao.IUserDao.saveUser",user);
//3.提交事务
sqlSession.commit();
//4.释放资源
sqlSession.close();
}
public void updateUser(User user) {
//1.使用SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用sqlSession中的方法,实现查询列表
sqlSession.update("com.ywb.dao.IUserDao.updateUser",user);
//3.提交事务
sqlSession.commit();
//4.释放资源
sqlSession.close();
}
public void deleteUser(Integer userId) {
//1.使用SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用sqlSession中的方法,实现查询列表
sqlSession.delete("com.ywb.dao.IUserDao.deleteUser",userId);
//3.提交事务
sqlSession.commit();
//4.释放资源
sqlSession.close();
}
public User findById(Integer userId) {
//1.使用SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用sqlSession中的方法,实现查询列表
User user = sqlSession.selectOne("com.ywb.dao.IUserDao.findById", userId);
//3.提交事务
sqlSession.commit();
//4.释放资源
sqlSession.close();
return user;
}
public List<User> findByName(String username) {
//1.使用SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用sqlSession中的方法,实现查询列表
List<User> users = sqlSession.selectList("com.ywb.dao.IUserDao.findByName", username);
//3.提交事务
sqlSession.commit();
//4.释放资源
sqlSession.close();
return users;
}
public int findTotal() {
//1.使用SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
//2.调用sqlSession中的方法,实现查询列表
int count = sqlSession.selectOne("com.ywb.dao.IUserDao.findTotal");
//3.提交事务
sqlSession.commit();
//4.释放资源
sqlSession.close();
return count;
}
}
<?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">
<mapper namespace="com.ywb.dao.IUserDao">
<!--查询所有-->
<select id="findAll" resultType="com.ywb.domain.User">
select * from user;
</select>
<!--插入用户-->
<insert id="saveUser" parameterType="com.ywb.domain.User">
<!--配置插入后,获取插入数据的id-->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user (username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address});
</insert>
<!--更新用户-->
<update id="updateUser" parameterType="com.ywb.domain.User">
update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id};
</update>
<!--删除用户-->
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{uid};
</delete>
<!--根据用户id查询用户-->
<select id="findById" parameterType="java.lang.Integer" resultType="com.ywb.domain.User">
select * from user where id = #{uid};
</select>
<!--根据名称模糊查询-->
<select id="findByName" parameterType="java.lang.String" resultType="com.ywb.domain.User">
<!--select * from user where username like #{name};-->
select * from user where username like '%${value}%' ;
</select>
<!--查询用户的总记录条数-->
<select id="findTotal" resultType="int">
select count(id) from user;
</select>
</mapper>
import com.ywb.dao.IUserDao;
import com.ywb.dao.impl.UserDaoImpl;
import com.ywb.domain.User;
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 org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
/**
* @author yang
* @date 2021年07月18日 17:35
*
* 测试mybatis的CRUD操作
*/
public class MybatisTest {
private InputStream in;
private IUserDao userDao;
@Before
public void init() throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.使用工厂对象创建dao对象
userDao = new UserDaoImpl(factory);
}
@After
public void destroy() throws Exception{
//6.释放资源
in.close();
}
@Test
public void testFindAll(){
//5.执行查询方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testSave(){
User user = new User();
user.setUsername("testMybatis dao");
user.setAddress("河北");
user.setSex("女");
user.setBirthday(new Date());
//5.执行查询方法
userDao.saveUser(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setUsername("updateMybatis dao");
user.setAddress("广东深圳");
user.setSex("男");
user.setBirthday(new Date());
user.setId(52);
//5.执行查询方法
userDao.updateUser(user);
}
@Test
public void testDelete(){
//执行删除方法
userDao.deleteUser(55);
}
@Test
public void testFindOne(){
User user = userDao.findById(54);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> users = userDao.findByName("王");
for (User user : users) {
System.out.println(user);
}
}
@Test
public void testFindTotal(){
int total = userDao.findTotal();
System.out.println(total);
}
}
1.7、SqlMapConfig.xml配置文件
配置内容
SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)
properties标签
可以在标签内部配置连接数据库信息
<?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>
<!--配置properties-->
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis?useUnicode=true&characterEncoding=UTF8"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</properties>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/ywb/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
也可以通过属性引用外部配置文件信息
resource属性:用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy_mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true
jdbc.username=root
jdbc.password=123456
<?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>
<!--配置properties-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/ywb/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
url属性:要求按照URL的写法来写地址
URL:Uniform Resource Locator 统一资源定位符,可以唯一标识一个资源的位置
写法:协议 主机 端口 URI
http://localhost:8080/mybatisserver/demo1Servlet
URI:Uniform Resource Identifier 统一资源标识符,在应用中可以唯一定位一个资源
<!--配置properties-->
<properties url="file:///E:/Mybatis_project/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties"></properties>
**typeAliases和 **package标签
<?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>
<!--配置properties-->
<properties url="file:///E:/Mybatis_project/day02_eesy_01mybatisCRUD/src/main/resources/jdbcConfig.properties"></properties>
<!--使用typeAliases配置别名,它只能配置domain中的别名-->
<typeAliases>
<!--typeAliases用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就不再区分大小写-->
<!--<typeAlias type="com.ywb.domain.User" alias="user"></typeAlias>-->
<!--用于指定要配置的别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="com.ywb.domain"></package>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<!--<mapper resource="com/ywb/dao/IUserDao.xml"></mapper>-->
<!--package标签用于指向dao接口所在的包,当指定了之后,就不需要再写mapper以及resource或者class-->
<package name="com.ywb.dao"></package>
</mappers>
</configuration>
1.8、连接池
连接池就是用于存储连接的一个容器
容器其实就是一个集合对象,该集合必须是线程安全的,不能两个线程拿到同一连接
该集合还必须实现队列的特性:先进先出
mybatis中的连接池:
mybatis连接池提供了3种方式的配置
配置的位置:
主配置文件SqlMapConfig.xml种的dataSource标签,type属性就是表示采用何种连接方式
type属性的取值:
POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
UNPOOLED 采用传统的获取连接的方式,虽然也实现javax.sql.DataSource接口,但是并没有使用池的思想
JNDI 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同服务器所能拿到的DataSource是不一样的,如果不是web或者maven的war工程,是不能使用的
1.9、事务
将事务改为自动提交的方法
openSession(true)
public void init() throws Exception{
//1.读取配置文件,生成字节输入流
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.获取SqlSession
sqlSession = factory.openSession(true);
//4.获取dao的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
1.10、映射文件的SQL深入
注意:
在配置文件中url用
jdbc:mysql://localhost:3306/eesy_mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true
在xml中url用
jdbc:mysql://localhost:3306/eesy_mybatis?useUnicode=true&characterEncoding=UTF8
标签使用
if标签:
<select id="findByCondition" parameterType="com.ywb.domain.User" resultType="com.ywb.domain.User">
select * from user where 1=1
<if test="username!=null">
and username = #{username}
</if>
<if test="sex!=null">
and sex = #{sex}
</if>
</select>
public void testFindByCondition(){
User u = new User();
u.setUsername("老王");
u.setSex("女");
List<User> users = userDao.findByCondition(u);
for (User user : users) {
System.out.println(user);
}
}
where标签:
<select id="findByCondition" parameterType="com.ywb.domain.User" resultType="com.ywb.domain.User">
select * from user
<where>
<if test="username!=null">
and username = #{username}
</if>
<if test="sex!=null">
and sex = #{sex}
</if>
</where>
</select>
foreach标签:
<select id="findUserInIds" resultType="com.ywb.domain.User" parameterType="com.ywb.domain.QueryVo">
select * from user
<where>
<if test="ids!=null and ids.size()>0">
<foreach collection="ids" open="and id in (" close=")" item="uid" separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
public void testFindUserInIds(){
QueryVo vo = new QueryVo();
LinkedList<Integer> list = new LinkedList<Integer>();
list.add(41);
list.add(42);
list.add(43);
list.add(44);
vo.setIds(list);
List<User> users = userDao.findUserInIds(vo);
for (User user : users) {
System.out.println(user);
}
}
sql标签:
<!--抽取重复的sql-->
<sql id="defaultUser">
select * from user
</sql>
<!--查询所有-->
<select id="findAll" resultType="com.ywb.domain.User">
<include refid="defaultUser"></include>
</select>
1.11、mybatis中的多表查询
例子:
account表
user表
要求:查询所有账户以及所属用户的用户名和地址
Account类(省略get/set方法):
/**
* @author yang
* @date 2021年07月20日 19:54
*/
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
}
AccountUser类(省略get/set方法):
/**
* @author yang
* @date 2021年07月21日 13:22
*/
public class AccountUser extends Account{
private String username;
private String address;
@Override
public String toString() {
return super.toString()+" AccountUser{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}
IAccountDao.xml
<?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">
<mapper namespace="com.ywb.dao.IAccountDao">
<!--查询所有用户以及所属的-->
<select id="findAllAccount" resultType="com.ywb.domain.AccountUser">
select a.*, u.username, u.address from user u , account a where u.id = a.uid
</select>
</mapper>
要求:查询所有账户以及所属用户的信息
Account类:
/**
* @author yang
* @date 2021年07月20日 19:54
*/
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
@Override
public String toString() {
return "Account{" +
"id=" + id +
", uid=" + uid +
", money=" + money +
'}';
}
}
IAccountDao.xml
<?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">
<mapper namespace="com.ywb.dao.IAccountDao">
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="com.ywb.domain.Account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<association property="user" column="user" javaType="com.ywb.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
</association>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="accountUserMap" resultType="com.ywb.domain.Account">
select u.*,a.`ID` AS aid, a.`UID`, a.`MONEY` FROM user u, account a WHERE u.`id` = a.`UID`
</select>
</mapper>
测试类:
@Test
public void testFindAll(){
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println(account);
System.out.println(account.getUser());
}
}
要求:查找所有用户以及用户名下的账户
User类:
/**
* @author yang
* @date 2021年07月18日 14:31
*/
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
//一对多关系映射:主表应该包含从表实体类的集合引用
private List<Account> accounts;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", sex='" + sex + '\'' +
", birthday=" + birthday +
'}';
}
}
IUserDao.xml
<?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">
<mapper namespace="com.ywb.dao.IUserDao">
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.ywb.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<!--配置user对象中accounts集合的映射-->
<collection property="accounts" ofType="com.ywb.domain.Account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
</collection>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM USER u LEFT OUTER JOIN account a ON u.`id` = a.`UID`
</select>
</mapper>
测试类:
public void testFindAll(){
//5.执行查询方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println("-------------每一个用户的信息-------------");
System.out.println(user);
System.out.println(user.getAccounts());
}
}
user表:
role表:
user_role表:
要求:查询各角色以及角色所赋予的用户信息
Role类:
public class Role {
private Integer roleId;
private String roleName;
private String roleDesc;
//多对多的映射关系:一个角色可以赋予多个用户
private List<User> users;
}
User类:
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
}
IRoleDao.xml
<?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">
<mapper namespace="com.ywb.dao.IRoleDao">
<!--定义role表的resultMap-->
<resultMap id="roleMap" type="com.ywb.domain.Role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
<collection property="users" ofType="com.ywb.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
</collection>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="roleMap" resultType="com.ywb.domain.Role">
SELECT r.id AS rid,r.`ROLE_NAME`,r.`ROLE_DESC`,u.* FROM role r
LEFT JOIN user_role ur ON r.`ID` = ur.`RID`
LEFT JOIN user u ON ur.`UID` = u.`id`
</select>
</mapper>
测试类:
@Test
public void testFindAll(){
//5.执行查询方法
List<Role> roles = roleDao.findAll();
for (Role role : roles) {
System.out.println(role);
System.out.println(role.getUsers());
}
}
要求:查询用户以及用户所拥有的角色信息
User类:
public class User implements Serializable {
private Integer id;
private String username;
private String address;
private String sex;
private Date birthday;
private List<Role> roles;
}
IUserDao.xml:
<?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">
<mapper namespace="com.ywb.dao.IUserDao">
<!--定义User的resultMap-->
<resultMap id="userAccountMap" type="com.ywb.domain.User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="sex" column="sex"></result>
<result property="address" column="address"></result>
<result property="birthday" column="birthday"></result>
<collection property="roles" ofType="com.ywb.domain.Role">
<id property="roleId" column="rid"></id>
<result property="roleName" column="role_name"></result>
<result property="roleDesc" column="role_desc"></result>
</collection>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="userAccountMap">
SELECT u.*,r.id AS rid,r.`ROLE_NAME`,r.`ROLE_DESC` FROM user u
LEFT outer JOIN user_role ur ON u.`id` = ur.`UID`
LEFT outer JOIN role r ON ur.`RID` = r.`ID`
</select>
</mapper>
测试类:
public void testFindAll(){
//5.执行查询方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
System.out.println(user.getRoles());
}
}
1.12、延迟加载和立即加载
延迟加载:在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)
立即加载:不管用不用,只要一调用方法,马上发起查询
一对多,多对多:通常情况下是延迟加载
一对一,多对一:通常情况下是立即加载
SqlMapConfig.xml配置文件
<?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>
<!--配置properties-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置参数-->
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"></setting>
</settings>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/ywb/dao/IUserDao.xml"/>
<mapper resource="com/ywb/dao/IAccountDao.xml"/>
</mappers>
</configuration>
IAccountDao.xml
<?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">
<mapper namespace="com.ywb.dao.IAccountDao">
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="com.ywb.domain.Account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--一对一的关系映射:配置封装user的内容
select属性指定的内容:查询用户唯一的标识
column属性指定的内容:用户根据id查询时,所需要的参数的值
-->
<association property="user" column="uid" select="com.ywb.dao.IUserDao.findById"></association>
</resultMap>
<!--查询所有-->
<select id="findAll" resultMap="accountUserMap" resultType="com.ywb.domain.Account">
SELECT * from account
</select>
</mapper>
IUserDao.xml
<?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">
<mapper namespace="com.ywb.dao.IUserDao">
<!--根据用户id查询用户-->
<select id="findById" parameterType="java.lang.Integer" resultType="com.ywb.domain.User">
select * from user where id = #{uid};
</select>
</mapper>
1.13、Mybatis中的缓存
缓存:存在于内存中的临时数据
作用:减少和数据库的交互次数,提高执行效率
适用于缓存:经常查询并且不经常改变的,数据的正确与否对最终结果影响不大
不适用于缓存:经常改变的数据,数据的正确与否对最终结果影响很大的
例如:商品库存,银行汇率,股市牌价
一级缓存:Mybatis中SqlSession对象的缓存
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去SqlSession中查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了
一级缓存是SqlSession范围的缓存,当调用SqlSession的修改,添加,删除,commit(),close() 等方法时,就会清空一级缓存
public void testFindOne(){
User user1 = userDao.findById(41);
System.out.println(user1);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);//true
}
public void testFindOne(){
User user1 = userDao.findById(41);
System.out.println(user1);
//关闭sqlsession
// sqlSession.close();
// sqlSession = factory.openSession();
//清空缓存
sqlSession.clearCache();
userDao = sqlSession.getMapper(IUserDao.class);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1 == user2);//false
}
public void testClearCache(){
User user1 = userDao.findById(41);
System.out.println(user1);
user1.setUsername("update user clear cache");
user1.setAddress("测试地址");
userDao.updateUser(user1);
User user2 = userDao.findById(41);
System.out.println(user2);
System.out.println(user1==user2);//false
}
二级缓存:Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
SqlMapConfig.xml
<?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>
<!--配置properties-->
<properties resource="jdbcConfig.properties"></properties>
<!--配置参数-->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/ywb/dao/IUserDao.xml"/>
</mappers>
</configuration>
IUserDao.xml
<?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">
<mapper namespace="com.ywb.dao.IUserDao">
<!--开启user支持二级缓存-->
<cache/>
<!--查询所有-->
<select id="findAll" resultType="com.ywb.domain.User">
SELECT * FROM USER
</select>
<!--根据用户id查询用户-->
<select id="findById" parameterType="java.lang.Integer" resultType="com.ywb.domain.User" useCache="true">
select * from user where id = #{uid};
</select>
</mapper>
测试类:
public void testClearCache(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();
System.out.println(user1==user2);//false
//原因:二级缓存存放的是数据,不是对象
}
1.14、Mybatis的注解开发
简单CRUD操作
package com.ywb.dao;
import com.ywb.domain.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/**
* @author yang
* @date 2021年07月23日 15:55
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
List<User> findAll();
/**
* 保存用户
* @param user
*/
@Insert("insert into user (username,sex,address,birthday) values(#{username},#{sex},#{address},#{birthday})")
void saveUser(User user);
/**
* 更新用户信息
* @param user
*/
@Update("update user set username = #{username}, sex = #{sex}, address = #{address}, birthday = #{birthday} where id = #{id}")
void updateUser(User user);
/**
* 删除用户
* @param userId
*/
@Delete("delete from user where id = #{id}")
void deleteUser(Integer userId);
/**
* 根据用户id查询用户
* @param userId
* @return
*/
@Select("select * from user where id = #{id}")
User findById(Integer userId);
/**
* 根据名称模糊查询用户
* @param username
* @return
*/
@Select("select * from user where username like #{username}")
List<User> findByUsername(String username);
/**
* 统计总用户数量
* @return
*/
@Select("select count(*) from user")
Integer findTotalUser();
}
用注解开发一对一的配置:
User类:
public class User implements Serializable {
private Integer userId;
private String userName;
private String userSex;
private String userAddress;
private Date userBirthday;
}
Account类:
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
IUserDao
public interface IUserDao {
/**
* 根据用户id查询用户
* @param userId
* @return
*/
@Select("select * from user where id = #{id}")
@Results(id = "userMap",value = {
@Result(id = true,property = "userId",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "userSex",column = "sex"),
@Result(property = "userAddress",column = "address"),
@Result(property = "userBirthday",column = "birthday")
})
User findById(Integer userId);
}
IAccountDao
public interface IAccountDao {
/**
* 查出所有账户和其所属的用户信息
* @return
*/
@Select("select * from account")
@Results(id = "accountMap",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "uid",column = "uid"),
@Result(property = "money",column = "money"),
@Result(property = "user",column = "uid",one = @One(select = "com.ywb.dao.IUserDao.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();
}
测试类:
@Test
public void testFindAll(){
List<Account> accounts = accountDao.findAll();
for (Account account : accounts) {
System.out.println(account);
System.out.println(account.getUser());
}
}
注解开发一对多的配置:
User类:
public class User implements Serializable {
private Integer userId;
private String userName;
private String userSex;
private String userAddress;
private Date userBirthday;
private List<Account> accounts;
}
Account类:
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
}
IAccountDao
package com.ywb.dao;
import com.ywb.domain.Account;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
/**
* @author yang
* @date 2021年07月23日 17:00
*/
public interface IAccountDao {
/**
* 根据uid查找账户
* @param uid
* @return
*/
@Select("select * from account where uid = #{uid}")
@Results(id = "accountMap",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "uid",column = "uid"),
@Result(property = "money",column = "money"),
})
List<Account> findByUid(Integer uid);
}
IUserDao
package com.ywb.dao;
import com.ywb.domain.User;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
/**
* @author yang
* @date 2021年07月23日 15:55
*/
public interface IUserDao {
/**
* 查询所有用户
* @return
*/
@Select("select * from user")
@Results(id = "userMap",value = {
@Result(id = true,property = "userId",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "userSex",column = "sex"),
@Result(property = "userAddress",column = "address"),
@Result(property = "userBirthday",column = "birthday"),
@Result(property = "accounts",column = "id",many = @Many(select = "com.ywb.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
})
List<User> findAll();
}
注解开发二级缓存
在接口处使用以下注解
@CacheNamespace(blocking = true)
@CacheNamespace(blocking = true)
public interface IUserDao {
/**
* 根据用户id查询用户
* @param userId
* @return
*/
@Select("select * from user where id = #{id}")
@Results(id = "userMap",value = {
@Result(id = true,property = "userId",column = "id"),
@Result(property = "userName",column = "username"),
@Result(property = "userSex",column = "sex"),
@Result(property = "userAddress",column = "address"),
@Result(property = "userBirthday",column = "birthday"),
@Result(property = "accounts",column = "id",many = @Many(select = "com.ywb.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
})
User findById(Integer userId);
}
测试类
public class SecondLevelCacheTest {
private InputStream in;
private SqlSessionFactory factory;
@Before
public void init() throws Exception{
in = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(in);
}
@After
public void destroy() throws Exception{
in.close();
}
@Test
public void testFindOne(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();
}
/*
只发起了一次查询请求
*/
}
第二章 Spring
2.1、概述
Spring是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以IOC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层Spring MVC 和 持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE企业应用开源框架
2.2、spring的优势
方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用
AOP编程的支持
通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持
降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些API的使用难度大为降低
Java源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码无疑是 Java 技术的最佳实践的范例。
2.3、spring的体系结构
2.4、程序的耦合
耦合:程序间的依赖关系
包括:类之间的依赖
方法间的依赖
解耦:降低程序间的依赖关系
实际开发中:应该做到编译器不依赖,运行时才依赖
解耦的思路:
第一步:使用反射来创建对象,而避免使用new关键字
第二步:通过读取配置文件来获取要创建的对象全限定类名
public class JdbcDemo01 {
public static void main(String[] args) throws Exception {
//1.注册驱动
//此处使用new方法时,若项目中没有导入mysqlJdbc数据包,则编译时就报错,此时称之为编译时依赖jar包
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//使用以下方式时,若没有导入jar包,则运行时才会报错,此时叫出现异常,运行时依赖jar包,当然,如果要使用其他类型数据库时,需要修改源码,因此最好使用配置文件来读取对象的全限定类名
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy", "root", "123456");
//3.获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4.执行SQL,获取结果集
ResultSet resultSet = pstm.executeQuery();
//5.遍历结果集
while (resultSet.next()){
System.out.println(resultSet.getString("name"));
}
//6.释放资源
resultSet.close();
pstm.close();
conn.close();
}
}
2.4.1、工厂模式解耦
bean.properties
accountService=com.ywb.service.impl.AccountServiceImpl
accountDao=com.ywb.dao.impl.AccountDaoImpl
BeanFactory
/**
* @author yang
* @date 2021年07月23日 20:09
* 一个创建Bean对象的工厂
*
* Bean:在计算机用语中,有可重用组件的含义
* JavaBean:用java语言编写的可重用组件
* javaBean > 实体类
*
* 它就是创建我们的service和dao对象的
* 第一步:需要一个配置文件来配置我们的service和dao
* 配置的内容:唯一标识=全限定类名(key=value)
* 配置文件可以是xml或者properties
* 第二步:通过读取配置文件中配置的内容,反射创建对象
*/
public class BeanFactory {
//定义一个properties对象
private static Properties props;
static {
try{
//实例化对象
props = new Properties();
//获取properties的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
}catch (Exception e){
throw new ExceptionInInitializerError("初始化properties失败");
}
}
/**
* 根据bean的名称来获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
bean = Class.forName(beanPath).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return bean;
}
}
使用:
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
AccountServiceImpl as = (AccountServiceImpl) BeanFactory.getBean("accountService");
分析:使用以上方法时,每次使用getBean方法都实例化一次对象,每次都是新的对象,为多例模式
改造:使用Map集合存储对象,只创建一次对象,创建后放在集合中,需要用的时候取出即可
/**
* @author yang
* @date 2021年07月23日 20:09
* 一个创建Bean对象的工厂
*
* Bean:在计算机用语中,有可重用组件的含义
* JavaBean:用java语言编写的可重用组件
* javaBean > 实体类
*
* 它就是创建我们的service和dao对象的
* 第一步:需要一个配置文件来配置我们的service和dao
* 配置的内容:唯一标识=全限定类名(key=value)
* 配置文件可以是xml或者properties
* 第二步:通过读取配置文件中配置的内容,反射创建对象
*/
public class BeanFactory {
/**
* 定义一个properties对象
*/
private static Properties props;
/**
* 定义一个容器,存放bean对象
*/
private static Map<String,Object> beans;
static {
try{
//实例化对象
props = new Properties();
//获取properties的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件中所有的key
Enumeration<Object> keys = props.keys();
//遍历keys
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取全限定类名
String beanPath = props.getProperty(key);
//通过反射创建对象
Object value = Class.forName(beanPath).newInstance();
//将key和value储存进map集合
beans.put(key,value);
}
}catch (Exception e){
throw new ExceptionInInitializerError("初始化properties失败");
}
}
/**
* 根据bean的名称来获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
2.5、Spring基于XML的ioc环境搭建和入门
控制反转:把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入和依赖查找
作用:削减计算机程序的耦合
环境搭建:
导入jar包
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ywb</groupId>
<artifactId>day01_eesy_03spring</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.4.RELEASE</version>
</dependency>
</dependencies>
</project>
配置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来管理-->
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.ywb.dao.impl.AccountDaoImpl"></bean>
</beans>
使用:
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
//方式一:强转类型
IAccountService accountService = (IAccountService) ac.getBean("accountService");
//方式二:传入对应类型的字节码
IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
System.out.println(accountService);
System.out.println(accountDao);
}
}
ApplicationContext的三个常用实现类:
-
ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了
-
FileSystemXmlApplicationContext:可以加载磁盘任意路径下的配置文件(必须有访问权限)
-
AnnotationConfigApplicationContext:用于读取注解创建容器的
核心容器的两个接口引发出的两个问题:
ApplicationContext:它在构建核心容器时,创建对象采取的思想是采用立即加载的方式,只要一读取完配置文件,马上就创建配置文件中配置的对象(单例对象适用)(通常使用这个)
BeanFactory:它在构建核心容器时,创建对象采取的思想是采用延迟加载的方式,什么时候根据id获取对象了,什么时候才真正地创建对象(多例对象适用)
三种创建bean对象的方式:
bean.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的管理细节
1.创建bean的三种方式
2.bean对象的作用范围
3.bean对象的生命周期
-->
<!--创建bean的三种方式-->
<!--第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl"></bean>
-->
<!--第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.ywb.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
-->
<!--第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)-->
<bean id="accountService" class="com.ywb.factory.StaticFactory" factory-method="getAccountService"></bean>
</beans>
对应第二种方式:
看成是jar包中的类
/**
* @author yang
* @date 2021年07月25日 1:02
* 模拟一个工厂类(该类可能是存在与jar包中的,我们无法通过修改源代码的方式来提供默认构造函数)
*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
对应第三种方式:
对应jar包中的类
/**
* @author yang
* @date 2021年07月25日 1:02
* 模拟一个工厂类(该类可能是存在与jar包中的,我们无法通过修改源代码的方式来提供默认构造函数)
*/
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
2.6、bean的作用范围和生命周期
bean标签的scope属性:用于指定bean的作用范围
取值:singleton:单例的(默认)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
常用:单例或多例
bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着
死亡:当对象长时间不用,且没有别的对象引用时,由java的垃圾回收机制回收
2.7、spring的依赖注入
依赖注入: Dependency Injection
IOC的作用: 降低程序间的耦合(依赖关系)
依赖关系的管理:
-
以后都交给spring来维护
-
在当前类需要使用到其他类的对象时,由spring来提供,我们只需要在配置文件中说明
依赖关系的维护:称之为依赖注入
依赖注入:
能注入的数据有三类:
基本类型和String
其他bean类型(在配置文件中或者注解中配置过的bean)
复杂类型/集合类型
注入的方式三种:
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供
构造函数注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的函数赋值。索引的位置从0开始
name:用于指定给构造函数中指定名称的参数赋值(常用)
以上三个用于指定给构造函数中哪个参数赋值=
value:用于提供基本类型数据和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
改变了这个bean对象的实例化方式,使我们在创建对象时如果用不到这些数据也必须提供
set方法注入(常用)
涉及的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定时所调用的set方法名称
value:用于提供基本类型数据和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行
bean.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中的依赖注入
依赖注入:
Dependency Injection
IOC的作用:
降低程序间的耦合(依赖关系)
依赖关系的管理:
以后都交给spring来维护
在当前类需要使用到其他类的对象时,由spring来提供,我们只需要在配置文件中说明
依赖关系的维护:
称之为依赖注入
依赖注入:
能注入的数据有三类:
基本类型和String
其他bean类型(在配置文件中或者注解中配置过的bean)
复杂类型/集合类型
注入的方式三种:
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供
-->
<!--构造函数注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index:用于指定要注入的数据给构造函数中指定索引位置的函数赋值。索引的位置从0开始
name:用于指定给构造函数中指定名称的参数赋值(常用)
====================以上三个用于指定给构造函数中哪个参数赋值=====================
value:用于提供基本类型数据和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
改变了这个bean对象的实例化方式,使我们在创建对象时如果用不到这些数据也必须提供
-->
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!--配置一个日期对象-->
<bean id="now" class="java.util.Date"></bean>
<!--set方法注入(常用)
涉及的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定时所调用的set方法名称
value:用于提供基本类型数据和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行
-->
<bean id="accountService2" class="com.ywb.service.impl.AccountServiceImpl2">
<property name="name" value="test"></property>
<property name="age" value="19"></property>
<property name="birthday" ref="now"></property>
</bean>
<!--复杂类型的注入/集合类型的注入
用于给list结构集合注入的标签:
list array set
用于给map结构集合注入的标签:
map props
结构相同,标签可以互换
-->
<bean id="accountService3" class="com.ywb.service.impl.AccountServiceImpl3">
<property name="myStr">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>CCC</value>
<value>DDD</value>
<value>EEE</value>
</list>
</property>
<property name="mySet">
<set>
<value>FFF</value>
<value>GGG</value>
<value>HHH</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>
</bean>
</beans>
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public AccountServiceImpl(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public void saveAccount() {
System.out.println("IAccountService中的方法执行。。。"+"--"+name+"--"+age+"--"+birthday);
}
}
public class AccountServiceImpl2 implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount() {
System.out.println("IAccountService中的方法执行。。。"+"--"+name+"--"+age+"--"+birthday);
}
}
public class AccountServiceImpl3 implements IAccountService {
private String[] myStr;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStr(String[] myStr) {
this.myStr = myStr;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void saveAccount() {
System.out.println(Arrays.toString(myStr));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
2.8、spring基于注解的开发
bean.xml的配置
<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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是在一个名称为
context的名称空间和约束中-->
<context:component-scan base-package="com.ywb"></context:component-scan>
</beans>
用于创建对象的注解
-
作用与在xml配置文件中编写一个标签的作用是一样的
-
Component:
-
作用:用于把当前类对象存入spring容器中
-
属性:value用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母小写
-
-
Controller:一般用在表现层
-
Service:一般用在业务层
-
Repository:一般用在持久层
以上三个注解的作用和属性与Component是一模一样的
他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
public void saveAccount() {
System.out.println("保存了账户");
}
}
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao1;
public void saveAccount() {
accountDao1.saveAccount();
}
}
用于注入数据的
-
作用与在xml配置文件中的bean标签写一个<property>标签一样
-
Autowired: - 作用:自动按照类型注入,只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功 - 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错 - 如果ioc容器中有多个类型匹配时,用变量名和id进行匹配,若没有匹配到则报错 - 出现位置:可以是变量上,也可以是方法上 - 细节:在使用注解注入时,set方法就不是必须的了
-
Qualifier(给在成员属性注解时需要和Autowired一起使用): - 作用:在按照类型注入基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以单独使用 - 属性:value用于指定注入bean的id
public class AccountServiceImpl implements IAccountService {
@Autowired
@Qualifier("accountDao")
private IAccountDao accountDao2;
public void saveAccount() {
accountDao2.saveAccount();
}
}
public QueryRunner createQueryRunner(@Qualifier("dataSource1") DataSource dataSource){
return new QueryRunner(dataSource);
}
-
Resource:
-
作用:直接按照bean的id注入。它可以独立使用
-
属性:name:用于指定name的id
以上三个注解都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现
-
public class AccountServiceImpl implements IAccountService {
@Resource(name = "accountDao")
private IAccountDao accountDao2;
public void saveAccount() {
accountDao2.saveAccount();
}
}
注意:集合类型的注入只能通过xml来实现
-
Value
-
作用:用于注入基本类型和String类型的数据
-
属性:value:用于指定数据的值。它可以使用spring中的SpEL(也就是spring的el表达式)
-
SpEL的写法:${表达式}
-
用于改变作用范围的
-
作用和在bean标签中使用scope属性实现的功能是一样的
-
Scope: - 作用:用于指定bean的作用范围 - 属性:value:指定范围的取值。常用值:singleton单例 prototype多例
和生命周期相关的(了解)
-
作用和在bean标签中使用init-method和destroy-method的作用是一样的
-
preDestroy - 作用:用于指定销毁方法
-
postConstruct - 作用:用于指定初始化方法
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Resource(name = "accountDao")
private IAccountDao accountDao2;
public void saveAccount() {
accountDao2.saveAccount();
}
@PostConstruct
public void init() {
System.out.println("初始化完成");
}
@PreDestroy
public void destroy() {
System.out.println("销毁完成");
}
}
public class Client {
/**
* 获取spring的IOC容器,并根据id获取对象
*
* @param args
*/
public static void main(String[] args) {
//1.获取核心容器对象
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService2 = (IAccountService) ac.getBean("accountService");
System.out.println(accountService2);
accountService2.saveAccount();
ac.close();
}
}
/*
结果:
初始化完成
com.ywb.service.impl.AccountServiceImpl@43d7741f
保存了账户
销毁完成
*/
2.9、使用注解代替xml文件
spring中的新注解
-
Configuration
- 作用:指定当前类是一个配置类
-
ComponentScan
-
作用:用于通过注解指定spring在创建容器时需要扫描的包
-
细节:当该类作为AnnotationConfigApplicationContext对象的创建参数时,可以不写
-
属性:
-
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
-
我们使用此注解就等同于在xml中配置了:
-
<context:component-scan base-package="com.ywb"></context:component-scan>
-
-
-
-
Bean
-
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
-
属性:
- name:用于指定bean的id。当不写时,默认值是当前方法的名称
-
细节
- 当我们用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象
- 查找的方式和Autowired注释的作用是一样的
-
-
Import
-
作用:用于导入其他的配置类
-
属性:
- value:用于指定其他配置类的字节码
-
当我们使用Import注解之后,有Import注解的类就是父配置类,而导入的都是子配置类
-
-
PropertySource
-
作用:用于指定properties文件的位置
-
属性:value:指定文件的名称和路径。
-
关键字:classpath:表示类路径下
-
//指定当前类是一个配置类
@Configuration
//指定spring在创建容器时需要扫描的包
@ComponentScan(basePackages = "com.ywb")
//导入其他的配置类
@Import(JdbcConfig.class)
//指定properties文件的位置
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
@Value("jdbc.driver")
private String driver;
@Value("jdbc.url")
private String url;
@Value("jdbc.username")
private String username;
@Value("jdbc.password")
private String password;
@Bean(name = "runner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean("dataSource")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUser("root");
ds.setPassword("123456");
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
测试类(使用AnnotationConfigApplicationContext):
public void testFindAll(){
//1.获取容器
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
//2.获取对象
IAccountService accountService = (IAccountService) ac.getBean("accountService");
//3.执行方法
List<Account> accounts = accountService.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
2.10、Spring整合junit
spring整合junit的配置
-
导入spring整合junit的jar(坐标)
-
使用Junit提供的一个注解(@Runwith)把原有的main方法替换了,替换成spring提供的
-
告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
@ContextConfiguration
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在的位置
当我们使用spring 5.x版本的时候,要求junit的jar包版本是4.1.2及以上
注解类使用示例:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void testFindAll(){
//3.执行方法
List<Account> accounts = accountService.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
}
xml文件使用示例:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService accountService;
@Test
public void testFindAll(){
//3.执行方法
List<Account> accounts = accountService.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
}
2.11、转账事务问题解决
ConnectionUtils类:
/**
* @author yang
* @date 2021年08月01日 1:20
* 连接的工具类,用于从数据源中获取一个连接,并且实现和线程的绑定
*/
public class ConnectionUtils {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
public Connection getThreadConnection(){
try {
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断conn是否为空
if (conn==null){
//3.从数据源中获取
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程绑定的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
TransactionManager类:
/**
* @author yang
* @date 2021年08月01日 1:31
* 和事务管理相关的工具类:开启事务,提交事务,回滚事务,释放连接
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try{
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try{
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try{
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try{
//还回池中
connectionUtils.getThreadConnection().close();
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
AccountDaoImpl类:
/**
* @author yang
* @date 2021年07月29日 21:37
* 账户的持久层实现类
*/
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void updateAccount(Account account) {
try {
runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
public Account findAccountByName(String accountName) {
try {
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?",new BeanListHandler<Account>(Account.class),accountName);
if (accounts==null || accounts.size()==0){
return null;
}
if (accounts.size() > 1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
AccountServiceImpl类:
/**
* @author yang
* @date 2021年07月29日 21:32
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
public void transfer(String sourceName, String targetName, Float money) {
try{
//1.开启事务
transactionManager.beginTransaction();
//2.执行操作
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
accountDao.updateAccount(source);
int i = 1/0;
//6.更新转入账户
accountDao.updateAccount(target);
//3.提交事务
transactionManager.commit();
}catch (Exception e){
//5.回滚事务
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
transactionManager.release();
}
}
}
bean.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">
<!--配置Service-->
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!--配置dao对象-->
<bean id="accountDao" class="com.ywb.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--配置ConnectionUtils-->
<bean id="connectionUtils" class="com.ywb.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionManager" class="com.ywb.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>
2.12、动态代理回顾
基于接口的动态代理
-
动态代理:
-
特点:字节码随用随创建,随用随加载
-
作用:不修改源码的基础上对方法进行增强
-
分类:
- 基于接口的动态代理
- 基于子类的动态代理
- 基于接口的动态代理:
-
涉及的类:Proxy
-
提供者:JDK官方
-
-
如何创建代理对象:
- 使用Proxy类中的newProxyInstance方法
-
创建代理对象的要求:
- 被代理类最少实现一个接口,如果没有则不能使用
-
newProxyInstance方法的参数:
-
ClassLoader:类加载器
- 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。(固定写法)
-
Class[]:字节码数组
- 它是用于让代理对象和被代理对象有相同的方法。(固定写法)
-
InvocationHandler:用于提供增强的代码
- 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
- 此接口的实现类都是谁用谁写
-
接口类:
/**
* @author yang
* @date 2021年08月01日 15:13
*/
public interface IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(Float money);
/**
* 售后
* @param money
*/
public void afterService(Float money);
}
实现类
/**
* @author yang
* @date 2021年08月01日 15:09
* 一个生产者
*/
public class Producer implements IProducer{
/**
* 销售
* @param money
*/
public void saleProduct(Float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(Float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
动态代理
/**
* @author yang
* @date 2021年08月01日 15:16
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(Producer.class.getClassLoader(), Producer.class.getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//获取参数
Float money = (Float)args[0];
if ("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(1000f);
}
}
基于子类的动态代理
public class Producer{
/**
* 销售
* @param money
*/
public void saleProduct(Float money){
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(Float money){
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
/**
* @author yang
* @date 2021年08月01日 15:16
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法进行增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
* 此接口的实现类都是谁用谁写
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
final Producer producer = new Producer();
Producer cglibProducer = (Producer) Enhancer.create(Producer.class, new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//获取参数
Float money = (Float)objects[0];
if ("saleProduct".equals(method.getName())){
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(1000f);
}
}
2.13、使用动态代理实现事务控制
BeanFactory:
/**
* @author yang
* @date 2021年08月02日 15:54
*/
public class BeanFactory {
@Autowired
private IAccountService accountService;
@Autowired
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public final void setAccountService(AccountServiceImpl accountService) {
this.accountService = accountService;
}
public IAccountService getAccountService(){
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try{
//1.开启事务
transactionManager.beginTransaction();
System.out.println("执行了开启事务");
//2.执行操作
System.out.println("即将执行invoke方法");
returnValue = method.invoke(accountService, args);
System.out.println("执行了invoke方法");
//3.提交事务
transactionManager.commit();
System.out.println("执行了事务提交方法");
//4.返回结果
System.out.println("返回结果");
return returnValue;
}catch (Exception e){
//5.回滚事务
System.out.println("回滚事务");
transactionManager.rollback();
throw new RuntimeException(e);
}finally {
//6.释放连接
System.out.println("释放连接");
transactionManager.release();
}
}
});
}
}
AccountServiceImpl:
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("转账开始");
//1.根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//3.转出账户减钱
source.setMoney(source.getMoney()-money);
//4.转入账户加钱
target.setMoney(target.getMoney()+money);
//5.更新转出账户
accountDao.updateAccount(source);
//int i = 1/0;
//6.更新转入账户
accountDao.updateAccount(target);
System.out.println("转帐结束");
}
bean.xml(重点:配置代理的Service类)
<?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">
<!--配置代理的AccountService-->
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService">
</bean>
<!--配置BeanFactory-->
<bean id="beanFactory" class="com.ywb.factory.BeanFactory">
<property name="accountService" ref="accountService"></property>
<property name="transactionManager" ref="transactionManager"></property>
</bean>
<!--配置Service-->
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl">
<!--注入dao-->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao对象-->
<bean id="accountDao" class="com.ywb.dao.impl.AccountDaoImpl">
<!--注入QueryRunner-->
<property name="runner" ref="runner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--配置ConnectionUtils-->
<bean id="connectionUtils" class="com.ywb.utils.ConnectionUtils">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="transactionManager" class="com.ywb.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
</beans>
2.14、AOP
-
含义
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
-
作用及优势
在程序运行期间,不修改源码对已有方法的增强
减少重复代码
提高开发效率
维护方便
-
实现方式:使用动态代理
-
AOP相关术语:
-
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
-
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义(被增强的)所有的切入点都是连接点,反之不然
-
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知
-
Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field
-
Target(目标对象):代理的目标对象(被代理对象)
-
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程
spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入
-
Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
-
Aspect(切面):是切入点和通知(引介)的结合
-
2.15、spring基于XML的AOP
spring中基于xml的AOP配置方式
- 把通知bean也交给spring管理
- 使用aop:config标签表明开始AOP的配置
- 使用aop:aspect标签表明配置切面
id属性:给切面提供一个唯一标识
ref属性:是指定通知类bean的id - 在aop:aspect标签的内部使用对应的标签来配置通知的类型
我们现在示例的是让printLog方法在切入点方法执行之前执行,为前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式含义指的是对业务层哪些方法增强
切入表达式的写法;
关键字:execution(表达式)
表达式:
访问修饰符 返回值 全限定类名.方法名(参数列表)
标准的表达式写法:
public void com.ywb.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.ywb.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任何返回值
* com.ywb.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.saveAccount()
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
* *..*.*(..)
全通配写法;
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.ywb.service.impl.*.*(..)
bean.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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<!--配置spring的ioc-->
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl"></bean>
<!--配置Logger类-->
<bean id="logger" class="com.ywb.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public void com.ywb.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
</beans>
五种通知方式的配置
<?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:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- bean definitions here -->
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl"></bean>
<bean id="logger" class="com.ywb.utils.Logger"></bean>
<aop:config>
<!--配置切入点表达式 id属性用于指定表达式的唯一标识 expression用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用
它还可以写在aop:aspect外面,此时就变成了所有切面可用(此时必须写在切面之前)
-->
<aop:pointcut id="pt1" expression="execution(* com.ywb.service.impl.*.*(..))"></aop:pointcut>
<aop:aspect id="logAdvice" ref="logger">
<!--前置通知:在切入点方法执行之前执行-->
<aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>
<!--后置通知:在切入点方法正常执行之后执行-->
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>
<!--异常通知:在切入点方法执行产生异常后执行-->
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>
<!--最终通知:无论切入点方法执行是否正常都会在后面执行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
<!--配置环绕通知-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
Logger类:
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知后,切入点方法没有执行,而通知方法执行了
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用
* 解决:
* spring框架为我们提供了一个接口:ProceedingJoinPoint 该接口有一个方法proceed(),就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
*/
public Object aroundPrintLog(ProceedingJoinPoint pjd){
Object returnValue;
try {
//得到方法执行所需的参数
Object[] args = pjd.getArgs();
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。前置");
//明确调用切入点方法
returnValue = pjd.proceed(args);
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。后置");
return returnValue;
}catch (Throwable t){
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。最终");
}
}
}
环绕通知:
- 问题:当我们配置了环绕通知后,切入点方法没有执行,而通知方法执行了
- 分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用
- 解决:
- spring框架为我们提供了一个接口:ProceedingJoinPoint 该接口有一个方法proceed(),就相当于明确调用切入点方法
- 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用
- spring中的环绕通知:它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式
2.16、spring基于注解的AOP
bean.xml文件
配置spring创建容器时要扫描的包
配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.ywb"></context:component-scan>
<!--配置spring开启注解aop的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
切面类Logger类:
/**
* @author yang
* @date 2021年08月04日 21:56
* 用于记录日志的工具类,提供了公共的代码
*/
@Component("logger")
@Aspect //表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.ywb.service.impl.*.*(..))")
private void pt1(){}
/**
* 前置通知
*/
@Before("pt1()")
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
@AfterReturning("pt1()")
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
@AfterThrowing("pt1()")
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
@After("pt1()")
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
/**
* 环绕通知
*/
//@Around("pt1()")
public Object aroundPrintLog(ProceedingJoinPoint pjd){
Object returnValue;
try {
//得到方法执行所需的参数
Object[] args = pjd.getArgs();
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。前置");
//明确调用切入点方法
returnValue = pjd.proceed(args);
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。后置");
return returnValue;
}catch (Throwable t){
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知Logger类中的aroundPrintLog方法开始记录日志了。。。最终");
}
}
}
tips:分开使用四种通知的注解时,最终通知会比异常通知和后置通知先执行
使用环绕通知则不会
2.17、spring中的JdbcTemplate
JdbcTemplate概述
它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多的操作模板类。
-
操作关系型数据的:
JdbcTemplate
HibernateTemplate
-
操作 nosql 数据库的:
RedisTemplate
-
操作消息队列的:
JmsTemplate
我们今天的主角在 spring-jdbc-5.0.2.RELEASE.jar 中,我们在导包的时候,除了要导入这个 jar 包外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)
JdbcTemplate作用
用于和数据库交互,实现对表的CRUD操作
最基本的用法
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
//准备数据源
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUsername("root");
ds.setPassword("123456");
//1.创建JdbcTemplate对象
JdbcTemplate jt = new JdbcTemplate();
//2.给jt设置数据源
jt.setDataSource(ds);
//3.执行操作
jt.execute("insert into account(`name`,`money`)values('ddd',1000)");
}
}
使用ioc配置
/**
* @author yang
* @date 2021年08月05日 21:03
* JdbcTemplate的CRUD操作
*/
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
//保存
jdbcTemplate.update("insert into account(name,money) values(?,?)","fff",1000f);
//更新
jdbcTemplate.update("update account set name=?,money=? where id=?","aaa",1000f,1);
//删除
jdbcTemplate.update("delete from account where id = ?",7);
//查询所有
//写法一
List<Account> accountList = jdbcTemplate.query("select * from account where money>?", new AccountRowMapper(), 900f);
//写法二
List<Account> accountList = jdbcTemplate.query("select * from account where money>?", new BeanPropertyRowMapper<Account>(Account.class), 900f);
for (Account account : accountList) {
System.out.println(account);
}
//查询一个
List<Account> accountList = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), 1);
System.out.println(accountList.isEmpty()?"查不到结果":accountList.get(0));
//查询返回一行一列(使用聚合函数,但不加group by子句)
Long count = jdbcTemplate.queryForObject("select count(*) from account where money>?", Long.class, 900f);
System.out.println(count);
}
}
/**
* 定义Account的封装策略
*/
class AccountRowMapper implements RowMapper<Account>{
/**
* 把结果集中的数据封装到Account中,任何由spring把每个Account加到集合中
* @param resultSet
* @param i
* @return
* @throws SQLException
*/
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
业务实例运用
/**
* @author yang
* @date 2021年08月05日 23:05
*/
public class AccountDaoImpl implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
if (accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
public Account findByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
if (accounts.isEmpty()){
return null;
}
if (accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
测试类
public class JdbcTemplateDemo4 {
public static void main(String[] args) {
//获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//获取对象
IAccountDao accountDao = (IAccountDao) ac.getBean("accountDao");
Account account = accountDao.findById(1);
System.out.println(account);
account.setMoney(900f);
accountDao.updateAccount(account);
}
}
配置类
<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="accountDao" class="com.ywb.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="datasource"></property>
</bean>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
JdbcDaoSupport 的使用
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
public Account findById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
if (accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
public Account findByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
if (accounts.isEmpty()){
return null;
}
if (accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
2.18、spring声明式事务控制
- JavaEE体系进行分层开发,事务处理位于业务层,spring提供了分层设计业务层的事务处理解决方案
- spring框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.0.2RELEASE.jar中
- spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
spring中基于xml的声明式事务控制配置步骤:
-
配置事务管理器
-
配置事务的通知
需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:id:给事务通知起一个唯一标志
transaction-manager:给事务通知提供一个事务管理器引用
-
配置aop中的通用切入点表达式
-
建立事务通知和切入点表达式的对应关系
-
配置事务的属性
是在事务的通知tx:advice标签的内部
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS
read-only:用于指定事务是否可读。只有查询方法才能设置为true,默认值是false,表示读写
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值,表示任何异常都回滚
bean.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="accountDao" class="com.ywb.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="datasource"></property>
</bean>
<bean id="accountService" class="com.ywb.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--spring中基于xml的声明式事务控制配置步骤
1.配置事务管理器
2.配置事务的通知
需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标志
transaction-manager:给事务通知提供一个事务管理器引用
3.配置aop中的通用切入点表达式
4.建立事务通知和切入点表达式的对应关系
5.配置事务的属性
是在事务的通知tx:advice标签的内部
-->
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性
isolation:用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS
read-only:用于指定事务是否可读。只有查询方法才能设置为true,默认值是false,表示读写
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值,表示任何异常都回滚
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.ywb.service.impl.*.*(..))"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
AccountDaoImpl类:
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
public Account findById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
if (accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
public Account findByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
if (accounts.isEmpty()){
return null;
}
if (accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
spring中基于注解的声明式事务控制配置步骤:
- 配置事务管理器
- 开启spring对注解事务的支持
- 在需要事务支持的地方使用@Transactional注解
bean.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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.ywb"></context:component-scan>
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- spring中基于注解 的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
-->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
AccountDaoImpl类:
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public Account findById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
if (accounts.isEmpty()){
return null;
}
return accounts.get(0);
}
public Account findByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
if (accounts.isEmpty()){
return null;
}
if (accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
需要事务控制的类AccountServiceImpl
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true) 只读配置
@Transactional(propagation = Propagation.REQUIRED,readOnly = false) 读写配置
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只读配置
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public Account findAccountById(Integer accountId) {
return accountDao.findById(accountId);
}
//配置读写
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
Account source = accountDao.findByName(sourceName);
Account target = accountDao.findByName(targetName);
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
accountDao.updateAccount(source);
//int i = 1/0;
accountDao.updateAccount(target);
}
}
spring中基于纯注解的声明式事务控制:
SpringConfiguration类
/**
* @author yang
* @date 2021年08月07日 17:22
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.ywb")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}
JdbcConfig类
/**
* @author yang
* @date 2021年08月07日 17:24
* 和连接数据库相关的配置类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate对象
* @param dataSource
* @return
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name = "datasource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
TransactionManager类
/**
* @author yang
* @date 2021年08月07日 18:20
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
第三章 SpringMVC
3.1、基本概述
- 三层架构
- 咱们开发服务器端程序,一般都基于两种形式,一种C/S架构程序,一种B/S架构程序
- 使用Java语言基本上都是开发B/S架构的程序,B/S架构又分成了三层架构
- 三层架构
- 表现层:WEB层,用来和客户端进行数据交互的。表现层一般会采用MVC的设计模型
- 业务层:处理公司具体的业务逻辑的
- 持久层:用来操作数据库的
- MVC模型
- MVC全名是Model View Controller 模型视图控制器,每个部分各司其职。
- Model:数据模型,JavaBean的类,用来进行数据封装。
- View:指JSP、HTML用来展示数据给用户
- Controller:用来接收用户的请求,整个流程的控制器。用来进行数据校验等。
3.2、SpringMVC的入门
搭建环境
- 创建web工程,导入开发的jar包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SPShldCq-1628929093533)(image-20210808224126733.png)]
坐标导入
<!-- 版本锁定 -->
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 配置核心的控制器(配置DispatcherServlet)
web.xml中
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<!--配置前端控制器-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- resources目录下创建springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启注解扫描-->
<context:component-scan base-package="com.ywb"></context:component-scan>
<!--配置视图解析对象-->
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!--开启SpringMVC框架注解的支持-->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
-
编写index.jsp和HelloController控制器类
-
index.jsp
<body> <h3>入门案例</h3> <a href="${ pageContext.request.contextPath }/hello">入门案例</a> </body>
-
HelloController
/** * @author yang * @date 2021年08月08日 23:21 * 控制器的类 */ @Controller public class HelloController { /** * 接收请求 * @return */ @RequestMapping(path = "/hello") public String sayHello(){ System.out.println("hello SpringMVC"); return "success"; } }
-
-
在WEB-INF目录下创建pages文件夹,编写success.jsp的成功页面
<body> <h3>入门成功!!</h3> </body>
-
启动tomcat服务器,进行测试
过程分析
- 入门案例的执行流程
- 当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象, 就会加载springmvc.xml配置文件
- 开启了注解扫描,那么HelloController对象就会被创建
- 从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解 找到执行的具体方法
- 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
- Tomcat服务器渲染页面,做出响应
- 入门案例中的组件分析
- 前端控制器(DispatcherServlet)
- 处理器映射器(HandlerMapping)
- 处理器(Handler)
- 处理器适配器(HandlAdapter)
- 视图解析器(View Resolver)
- 视图(View)
3.3、RequestMapping注解详解
- RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
- RequestMapping注解可以作用在方法和类上
- 作用在类上:第一级的访问目录
- 作用在方法上:第二级的访问目录
- 细节:路径可以不编写 / 表示应用的根目录开始
- ${ pageContext.request.contextPath }也可以省略不写,但是路径上不能写 /
- RequestMapping的属性
- path 指定请求路径的url
- value value属性和path属性是一样的
- method 指定该方法的请求方式
- params 指定限制请求参数的条件
- headers 发送的请求中必须包含的请求头
3.4、请求参数的绑定
-
请求参数的绑定说明
- 绑定机制
- 表单提交的数据都是k=v格式的 username=root&password=1234
- SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 要求:提交表单的name和参数的名称是相同的
- 支持的数据类型
- 基本数据类型和字符串类型
- 实体类型(JavaBean)
- 集合数据类型(List、map集合等)
- 绑定机制
-
基本数据类型和字符串类型
- 提交表单的name和参数的名称是相同的
- 区分大小写
-
实体类型(JavaBean)
- 提交表单的name和JavaBean中的属性名称需要一致
- 如果一个JavaBean类中包含其他的引用类型,那么表单的name属性需要编写成:对象.属性 例如: address.name
-
给集合属性数据封装
- JSP页面编写方式:list[0].属性
-
请求参数中文乱码的解决
-
在web.xml中配置Spring提供的过滤器类
<!--配置解决中文乱码的过滤器--> <filter> <filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
实体类型(JavaBean)代码示例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--请求参数绑定--%>
<%--
<a href="param/testParam?username=hehe&password=123">请求参数绑定</a>
--%>
<%--请求参数绑定封装到javaBean中
把数据封装到Account类中,Account类中有基本类型和User引用类型
--%>
<form action="param/saveAccount" method="post">
姓名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
金额:<input type="text" name="money"><br>
用户姓名:<input type="text" name="user.uname"><br>
用户年龄:<input type="text" name="user.age"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
ParamController控制器类:
/**
* @author yang
* @date 2021年08月09日 16:23
* 请求参数绑定
*/
@Controller
@RequestMapping("/param")
public class ParamController {
/**
* 请求参数绑定入门
* @return
*/
@RequestMapping("/testParam")
public String testParam(String username,String password){
System.out.println("testPram执行了。。。");
System.out.println("用户名:"+username);
System.out.println("密码:"+password);
return "success";
}
/**
* 请求参数绑定封装到JavaBean中
* @return
*/
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println("saveAccount执行了。。。");
System.out.println(account);
return "success";
}
}
集合类型代码示例:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--请求参数绑定封装到javaBean中
Account类型中有List集合和Map集合
--%>
<form action="param/saveAccount" method="post">
姓名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
金额:<input type="text" name="money"><br>
用户姓名:<input type="text" name="users[0].uname"><br>
用户年龄:<input type="text" name="users[0].age"><br>
用户姓名:<input type="text" name="userMap['one'].uname"><br>
用户年龄:<input type="text" name="userMap['one'].age"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
Account类:
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private List<User> users;
private Map<String,User> userMap;
}
-
自定义类型转换器
-
表单提交的任何数据类型全部都是字符串类型,但是后台定义Integer类型,数据也可以封装上,说明 Spring框架内部会默认进行数据类型转换。
-
如果想自定义数据类型转换,可以实现Converter的接口
-
自定义类型转换器
/** * @author yang * @date 2021年08月10日 15:56 * 自定义类型转换器 */ public class StringToDateConverter implements Converter<String,Date> { @Override public Date convert(String s) { if (s==null){ throw new RuntimeException("请您传入数据"); } SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); try { //把字符串转换为日期 return df.parse(s); }catch (Exception e){ throw new RuntimeException("数据类型转换出现错误"); } } }
-
注册自定义类型转换器,在springmvc.xml配置文件中编写配置
<!--配置自定义类型转换器--> <bean id="conversionService2" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="com.ywb.utils.StringToDateConverter"></bean> </set> </property> </bean> <!--开启SpringMVC框架注解的支持--> <mvc:annotation-driven conversion-service="conversionService2"></mvc:annotation-driven>
-
-
-
在控制器中使用原生的ServletAPI对象
- 只需要在控制器的方法参数定义HttpServletRequest和HttpServletResponse对象
3.5、常用的注解
-
RequestParam注解
-
作用:把请求中的指定名称的参数传递给控制器中的形参赋值
-
属性
- value:请求参数中的名称
- required:请求参数中是否必须提供此参数,默认值是true,必须提供
-
代码如下
/** * 测试RequestParam注解 * @return */ @RequestMapping("/testRequestParam") public String testRequestParam(@RequestParam(name = "name") String username){ System.out.println("testRequestParam执行了。。。"); System.out.println(username); return "success"; }
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <a href="anno/testRequestParam?name=哈哈">RequestParam</a> </body> </html>
-
-
RequestBody注解
-
作用:用于获取请求体的内容(注意:get方法不可以)
-
属性
- required:是否必须有请求体,默认值是true
-
注意点:在表单中需要加入
enctype="text/plain"
,否则会中文乱码 -
代码如下
/** * 测试RequestBody注解 * @return */ @RequestMapping("/testRequestBody") public String testRequestBody(@RequestBody String body){ System.out.println("testRequestBody执行了。。。"); System.out.println(body); return "success"; }
<form action="anno/testRequestBody" method="post" enctype="text/plain"> 用户姓名:<input type="text" name="username"><br> 用户年龄:<input type="text" name="age"><br> <input type="submit" value="提交"> </form>
-
-
PathVariable注解
-
作用:拥有绑定url中的占位符的。例如:url中有/delete/{id},{id}就是占位符
-
属性
- value:指定url中的占位符名称
-
Restful风格的URL
- 请求路径一样,可以根据不同的请求方式去执行后台的不同方法
- restful风格的URL优点
- 结构清晰
- 符合标准
- 易于理解
- 扩展方便
-
代码如下
/** * 测试PathVariable注解 * @return */ @RequestMapping("/testPathVariable/{sid}") public String testPathVariable(@PathVariable(value = "sid") String id){ System.out.println("testPathVariable执行了。。。"); System.out.println(id); return "success"; }
<a href="anno/testPathVariable/10">PathVariable</a>
-
-
RequestHeader注解
-
作用:获取指定请求头的值
-
属性
- value:请求头的名称
-
代码如下
/** * 测试RequestHeader注解 * @return */ @RequestMapping("/testRequestHeader") public String testRequestHeader(@RequestHeader("Accept") String header){ System.out.println("testRequestHeader执行了。。。"); System.out.println(header); return "success"; }
<a href="anno/testRequestHeader">RequestHeader</a>
-
-
CookieValue注解
-
作用:用于获取指定cookie的名称的值
-
属性
- value:cookie的名称
-
代码如下
/** * 测试CookieValue注解 * @return */ @RequestMapping("/testCookieValue") public String testCookieValue(@CookieValue(value = "JSESSIONID") String cookieValue){ System.out.println("testCookieValue执行了。。。"); System.out.println(cookieValue); return "success"; }
<a href="anno/testCookieValue">CookieValue</a>
-
-
ModelAttribute注解
-
作用
- 出现在方法上:表示当前方法会在控制器方法执行前先执行。
- 出现在参数上:获取指定的数据给参数赋值。
-
应用场景
- 当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。
-
具体的代码
-
修饰的方法有返回值
/** * 测试ModelAttribute注解 * @return */ @RequestMapping("/testModelAttribute") public String testModelAttribute(User user){ System.out.println("testModelAttribute执行了。。。"); System.out.println(user); return "success"; } /** * 该方法会先执行 * @param uname * @return */ @ModelAttribute public User showUser(String uname){ System.out.println("showUser方法执行了。。。"); //模拟数据库查询对象 User user = new User(); user.setUname(uname); user.setAge(20); user.setDate(new Date()); return user; }
-
修饰的方法没有返回值
/** * 测试ModelAttribute注解 * @return */ @RequestMapping("/testModelAttribute") public String testModelAttribute(@ModelAttribute("abc") User user){ System.out.println("testModelAttribute执行了。。。"); System.out.println(user); return "success"; } /** * 没有返回值类型 * @param uname * @return */ @ModelAttribute public void showUser(String uname, Map<String,User> map){ System.out.println("showUser方法执行了。。。"); User user = new User(); user.setUname(uname); user.setAge(20); user.setDate(new Date()); map.put("abc",user); }
-
-
SessionAttributes注解
-
作用:用于多次执行控制器方法间的参数共享
-
属性
- value:指定存入属性的名称
-
代码如下
/** * @author yang * @date 2021年08月10日 16:27 */ @Controller @RequestMapping("/anno") @SessionAttributes(value = {"msg"}) //存入Session域的对象中 public class AnnoController { /** * 向request中存入值 * SessionAttributes注解 * @return */ @RequestMapping("/testSessionAttributes") public String testSessionAttributes(Model model){ System.out.println("testSessionAttributes执行了。。。"); model.addAttribute("msg","测试SessionAttributes"); return "success"; } /** * 从session中获取值 * SessionAttributes注解 * @return */ @RequestMapping("/getSessionAttributes") public String getSessionAttributes(ModelMap modelMap){ System.out.println("getSessionAttributes执行了。。。"); String msg = (String) modelMap.get("msg"); System.out.println("msg是:"+msg); return "success"; } /** * 清除session * SessionAttributes注解 * @return */ @RequestMapping("/delSessionAttributes") public String delSessionAttributes(SessionStatus status){ System.out.println("delSessionAttributes执行了。。。"); status.setComplete(); return "success"; } }
-
-
3.6、响应数据和结果视图
3.6.1、返回值分类
-
返回字符串
-
Controller方法返回字符串可以指定逻辑视图的名称,根据视图解析器为物理视图的地址。
@RequestMapping("/testString") public String testString(){ System.out.println("testString方法执行了。。。"); return "success"; }
-
具体的应用场景
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/testString") public String testString(Model model){ System.out.println("testString方法执行了。。。"); //模拟从数据库中查找数据 User user = new User(); user.setUsername("root"); user.setPassword("1234"); user.setAge(19); model.addAttribute("user",user); return "success"; } }
<html> <head> <title>Title</title> </head> <body> <h3>执行成功</h3> ${user.username} ${user.password} ${user.age} </body> </html>
-
-
返回值是void
-
如果控制器的方法返回值编写成void,执行程序报404的异常,默认查找JSP页面没有找到。
-
默认会跳转到@RequestMapping(value="/initUpdate") initUpdate的页面。
-
可以使用请求转发或者重定向跳转到指定的页面
@RequestMapping("/testVoid") public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("testVoid方法执行了。。。"); //编写请求转发的程序 //request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response); //重定向 //response.sendRedirect(request.getContextPath()+"/index.jsp"); //设置中文乱码 response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); //直接进行响应 response.getWriter().print("你好"); return; }
-
-
-
返回值是ModelAndView对象
-
ModelAndView对象是Spring提供的一个对象,可以用来调整具体的JSP视图
-
代码如下
/** * 返回值是ModelAndView * @return */ @RequestMapping("/testModelAndView") public ModelAndView testModelAndView(){ System.out.println("testModelAndView方法执行了。。。"); //创建ModelAndView对象 ModelAndView mv = new ModelAndView(); //模拟从数据库中查找数据 User user = new User(); user.setUsername("root"); user.setPassword("1234"); user.setAge(19); //把user对象存储到mv对象中,也会把user对象存入到request对象 mv.addObject("user",user); //跳转到哪个页面 mv.setViewName("success"); return mv; }
-
3.6.2、SpringMVC框架提供的转发和重定向
-
forward请求转发
-
controller方法返回String类型,想进行请求转发也可以编写成
/** * 使用关键字的方法进行转发或重定向 * @return */ @RequestMapping("/testForwardOrRedirect") public String testForwardOrRedirect(){ System.out.println("testForwardOrRedirect方法执行了。。。"); //请求转发 return "forward:/WEB-INF/pages/success.jsp"; }
-
-
redirect重定向
-
controller方法返回String类型,想进行重定向也可以编写成
/** * 使用关键字的方法进行转发或重定向 * @return */ @RequestMapping("/testForwardOrRedirect") public String testForwardOrRedirect(){ System.out.println("testForwardOrRedirect方法执行了。。。"); //重定向 return "redirect:/index.jsp"; // return "redirect:/user/findAll"; }
-
3.6.3、ResponseBody响应json数据
-
DispatcherServlet会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而 不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置
-
mvc:resources标签配置不过滤
- location元素表示webapp目录下的包下的所有文件
- mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b
springmvc.xml:
<!--告诉前端配置器,哪些静态资源不拦截--> <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 --> <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript -->
-
使用@RequestBody获取请求体数据
<script> //页面加载,绑定单击事件 $(function () { $("#btn").click(function () { //alert("hello btn") //发送ajax请求 $.ajax({ //编写json格式,设置属性和值 url:"user/testAjax", contentType:"application/json;charset=UTF-8", data:'{"username":"测试ajax","password":"12345","age":19}', dataType:"json", type:"post", success:function (data) { } }) }) }) </script>
@RequestMapping(value = "/testAjax") public void testAjax(@RequestBody String body){ System.out.println("testAjax方法执行了。。。"); System.out.println(body); }
-
使用@RequestBody注解把json的字符串转换成JavaBean的对象,并把对象返回到data
/** * 模拟异步请求响应 */ @RequestMapping(value = "/testAjax") public @ResponseBody User testAjax(@RequestBody User user){ System.out.println("testAjax方法执行了。。。"); //客户端发送的ajax请求,传的是json字符串,spring框架把json字符串封装到user对象中 System.out.println(user); //做响应,模拟查找数据库 user.setUsername("查找的用户"); user.setAge(30); //做响应 return user; }
<script> //页面加载,绑定单击事件 $(function () { $("#btn").click(function () { //alert("hello btn") //发送ajax请求 $.ajax({ //编写json格式,设置属性和值 url:"user/testAjax", contentType:"application/json;charset=UTF-8", data:'{"username":"测试ajax","password":"12345","age":19}', dataType:"json", type:"post", success:function (data) { //data为服务器响应的json数据,进行解析 alert(data); alert(data.username); alert(data.password); alert(data.age); } }) }) }) </script>
-
json字符串和JavaBean对象互相转换的过程中,需要使用jackson的jar包
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>
-
3.7、SpringMVC实现文件上传
3.7.1、文件上传的回顾
-
导入文件上传的jar包
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency>
-
编写文件上传的JSP页面
<html> <head> <title>Title</title> </head> <body> <h3>文件上传</h3> <form action="user/fileupload1" method="post" enctype="multipart/form-data"> 选择文件<input type="file" name="upload"><br> <input type="submit" value="上传"> </form> </body> </html>
-
编写文件上传的Controller控制器
/** * 文件上传 * @return */ @RequestMapping("/fileupload1") public String fileupload1(HttpServletRequest request) throws Exception { System.out.println("fileupload1执行了。。。"); //使用fileupload组件帮助我们实现文件上传 //上传的位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); //判断该路径是否存在 File file = new File(path); if (!file.exists()){ file.mkdirs(); } //解析request对象,获取上传文件项 DiskFileItemFactory factory = new DiskFileItemFactory(); ServletFileUpload upload = new ServletFileUpload(factory); //解析request List<FileItem> items = upload.parseRequest(request); //遍历 for (FileItem item : items) { //判断当前item对象是否为上传文件项 if (item.isFormField()){ //普通表单项 System.out.println("普通表单项"); }else { //上传文件项 System.out.println("上传文件项"); //获取上传文件的名称 String fileName = item.getName(); //完成文件上传 item.write(new File(path,fileName)); //删除临时文件 item.delete(); } } return "success"; }
3.7.2、SpringMVC传统方式文件上传
-
SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的 name属性名称相同。
-
配置文件解析器对象
<!--配置文件解析器对象--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"></property> </bean>
-
代码如下
/** * SpringMVC文件上传 * @return */ @RequestMapping("/fileupload2") public String fileupload2(HttpServletRequest request, MultipartFile upload) throws Exception { System.out.println("springmvc文件上传。。。"); //使用fileupload组件帮助我们实现文件上传 //上传的位置 String path = request.getSession().getServletContext().getRealPath("/uploads/"); //判断该路径是否存在 File file = new File(path); if (!file.exists()){ file.mkdirs(); } //获取上传文件的名称 String filename = upload.getOriginalFilename(); upload.transferTo(new File(path,filename)); return "success"; }
3.7.3、SpringMVC跨服务器方式文件上传
-
搭建图片服务器
- 根据文档配置tomcat8的服务器,现在是2个服务器
- 创建多一个项目fileuploadserver作为图片服务器
-
实现SpringMVC跨服务器方式文件上传
-
导入开发需要的jar包
<dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-core</artifactId> <version>1.18.1</version> </dependency> <dependency> <groupId>com.sun.jersey</groupId> <artifactId>jersey-client</artifactId> <version>1.18.1</version> </dependency>
-
编写文件上传的JSP页面
<h3>跨服务器方式文件上传</h3> <form action="user/fileupload3" method="post" enctype="multipart/form-data"> 选择文件<input type="file" name="upload"><br> <input type="submit" value="上传"> </form>
-
编写控制器
/** * 跨服务器文件上传 * @return */ @RequestMapping("/fileupload3") public String fileupload3(MultipartFile upload) throws Exception { System.out.println("跨服务器文件上传。。。"); //定义上传文件服务器路径 String path = "http://localhost:9090/uploads/"; //获取上传文件的名称 String filename = upload.getOriginalFilename(); //创建客户端服务器 Client client = Client.create(); //和图片服务器进行连接 WebResource resource = client.resource(path + filename); //上传文件 resource.put(upload.getBytes()); return "success"; }
-
3.8、SpringMVC的异常处理
-
异常处理思路
- Controller调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet找异常处理器进 行异常的处理。
-
SpringMVC的异常处理
-
自定义异常类
/** * @author yang * @date 2021年08月12日 16:04 * 自定义异常类 */ public class SysException extends Exception{ private String msg; public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public SysException(String msg) { this.msg = msg; } }
-
自定义异常处理器
/** * @author yang * @date 2021年08月12日 16:12 * 异常处理器 */ public class SysExceptionResolver implements HandlerExceptionResolver { /** * 处理异常业务逻辑 * @param httpServletRequest * @param httpServletResponse * @param o * @param e * @return */ @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { //获取到异常对象 SysException sysException = null; if (e instanceof SysException){ sysException = (SysException) e; }else { sysException = new SysException("系统正在维护..."); } //创建ModelAndView对象 ModelAndView mv = new ModelAndView(); mv.addObject("errorMsg",sysException.getMsg()); //跳转的页面 mv.setViewName("error"); return mv; } }
-
配置异常处理器
<!--配置异常处理器对象--> <bean id="sysExceptionResolver" class="com.ywb.exception.SysExceptionResolver"></bean>
-
3.9、SpringMVC框架中的拦截器
-
拦截器的概述
- SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。
- 可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链 中的拦截器会按着定义的顺序执行。
- 拦截器和过滤器的功能比较类似,有区别
- 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
- 拦截器是SpringMVC框架独有的。
- 过滤器配置了/*,可以拦截任何资源。
- 拦截器只会对控制器中的方法进行拦截。
- 拦截器也是AOP思想的一种实现方式
- 想要自定义拦截器,需要实现HandlerInterceptor接口。
-
自定义拦截器步骤
-
创建类,实现HandlerInterceptor接口,重写需要的方法
/** * @author yang * @date 2021年08月12日 16:48 * 自定义拦截器 */ public class MyInterceptor1 implements HandlerInterceptor { /** * 预处理,controller方法执行前 * return true 放行,执行下一个拦截器,没有则执行controller方法 * return false 不放行 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("MyInterceptor1执行了...预处理111"); //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response); //return false; return true; } /** * 后处理方法,controller方法执行后,success.jsp执行之前 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("MyInterceptor1执行了...后处理111"); //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response); } /** * success.jsp页面执行后,该方法会执行 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("MyInterceptor1执行了...最后111"); } }
-
在springmvc.xml中配置拦截器类
<!--配置拦截器--> <mvc:interceptors> <!--配置第一个拦截器--> <mvc:interceptor> <!--要拦截的具体方法--> <mvc:mapping path="/user/*"/> <!--不要拦截的方法 <mvc:exclude-mapping path=""/> --> <!--配置拦截器对象--> <bean class="com.ywb.interceptor.MyInterceptor1"></bean> </mvc:interceptor> <!--配置第二个拦截器--> <mvc:interceptor> <!--要拦截的具体方法--> <mvc:mapping path="/user/*"/> <!--不要拦截的方法 <mvc:exclude-mapping path=""/> --> <!--配置拦截器对象--> <bean class="com.ywb.interceptor.MyInterceptor2"></bean> </mvc:interceptor> </mvc:interceptors>
-
HandlerInterceptor接口中的方法
- preHandle方法是controller方法执行前拦截的方法
- 可以使用request或者response跳转到指定的页面
- return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法。
- return false不放行,不会执行controller中的方法。
- postHandle是controller方法执行后执行的方法,在JSP视图执行前。
- 可以使用request或者response跳转到指定的页面
- 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
- postHandle方法是在JSP执行后执行
- request或者response不能再跳转页面了
- preHandle方法是controller方法执行前拦截的方法
-
第四章 整合ssm
4.1、搭建整合环境
-
搭建整合环境
-
整合说明:SSM整合可以使用多种方式,此处选择XML + 注解的方式
-
整合的思路
- 先搭建整合的环境
- 先把Spring的配置搭建完成
- 再使用Spring整合SpringMVC框架
- 最后使用Spring整合MyBatis框架
-
创建数据库和表结构
create database ssm; use ssm; create table account( id int primary key auto_increment, name varchar(20), money double );
-
创建maven的工程
-
web依赖于service,service依赖于dao,dao依赖于domain
-
在ssm_parent的pom.xml文件中引入坐标依赖
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>5.0.2.RELEASE</spring.version> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.12</log4j.version> <mysql.version>5.1.6</mysql.version> <mybatis.version>3.4.5</mybatis.version> </properties> <dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.6.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- log start --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> </dependency> <!-- log end --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> <type>jar</type> <scope>compile</scope> </dependency> </dependencies>
-
-
编写实体类,在ssm_domain项目中编写
/** * @author yang * @date 2021年08月12日 18:21 * 账户 */ public class Account implements Serializable { private Integer id; private String name; private Double money; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
-
编写dao接口
/** * @author yang * @date 2021年08月12日 18:23 * 账户dao接口 */ public interface IAccountDao { /** * 查询所有账户 * @return */ public List<Account> findAll(); /** * 保存账户 * @param account */ public void saveAccount(Account account); }
-
编写service接口和实现类
/** * @author yang * @date 2021年08月12日 18:28 */ public interface IAccountService { /** * 查询所有账户 * @return */ public List<Account> findAll(); /** * 保存账户 * @param account */ public void saveAccount(Account account); }
/** * @author yang * @date 2021年08月12日 18:29 */ @Service("accountService") public class AccountServiceImpl implements IAccountService { @Override public List<Account> findAll() { System.out.println("业务层:查询所有账户..."); return null; } @Override public void saveAccount(Account account) { System.out.println("业务层:保存账户..."); } }
-
4.2、Spring框架代码的编写
-
搭建和测试Spring的开发环境
-
在ssm_web项目中创建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" 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 http://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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--开启注解的扫描,扫描service和dao,不扫描controller--> <context:component-scan base-package="com.ywb"> <!--配置哪些注解不扫描--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter> </context:component-scan> </beans>
-
在ssm_web项目中编写测试方法,进行测试
/** * @author yang * @date 2021年08月12日 19:36 */ public class TestSpring { @Test public void run1(){ //加载配置文件 ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); //获取对象 IAccountService accountService = (IAccountService) ac.getBean("accountService"); //执行方法 List<Account> all = accountService.findAll(); } }
-
4.3、Spring整合SpringMVC框架
-
搭建和测试SpringMVC的开发环境
-
在web.xml中配置DispatcherServlet前端控制器和过滤器
<!--配置前端控制器--> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!--配置解决中文乱码--> <filter> <filter-name>filter</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>filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
-
创建springmvc.xml的配置文件,编写配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--开启注解扫描,只扫描Controller注解--> <context:component-scan base-package="com.ywb"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:include-filter> </context:component-scan> <!--配置视图解析对象--> <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!-- JSP文件所在的目录 --> <property name="prefix" value="/WEB-INF/pages/"></property> <!-- 文件的后缀名 --> <property name="suffix" value=".jsp"></property> </bean> <!--告诉前端配置器,哪些静态资源不拦截--> <mvc:resources location="/css/" mapping="/css/**"/> <!-- 样式 --> <mvc:resources location="/images/" mapping="/images/**"/> <!-- 图片 --> <mvc:resources location="/js/" mapping="/js/**"/> <!-- javascript --> <!--开启SpringMVC框架注解的支持--> <mvc:annotation-driven></mvc:annotation-driven> </beans>
-
测试SpringMVC的框架搭建是否成功
-
编写index.jsp和list.jsp编写,超链接
<a href="account/findAll">测试</a>
-
创建AccountController类,编写方法,进行测试
/** * @author yang * @date 2021年08月12日 18:31 */ @Controller @RequestMapping("/account") public class AccountController { @RequestMapping("/findAll") public String findAll(){ System.out.println("表现层:查询所有账户..."); return "list"; } }
-
-
-
Spring整合SpringMVC的框架
-
目的:在controller中能成功的调用service对象中的方法。
-
在项目启动的时候,就去加载applicationContext.xml的配置文件,在web.xml中配置 ContextLoaderListener监听器(该监听器只能加载WEB-INF目录下的applicationContext.xml的配置文 件)。
<!--配置spring的监听器,默认只加载WEB-INF目录下的applicationContext.xml配置文件--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--设置配置文件的路径--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param>
-
在controller中注入service对象,调用service对象的方法进行测试、
/** * @author yang * @date 2021年08月12日 18:31 */ @Controller @RequestMapping("/account") public class AccountController { @Autowired private IAccountService accountService; @RequestMapping("/findAll") public String findAll(){ System.out.println("表现层:查询所有账户..."); //调用service方法 accountService.findAll(); return "list"; } }
-
4.4、Spring整合MyBatis框架
-
搭建和测试MyBatis的环境
-
在web项目中编写SqlMapConfig.xml的配置文件,编写核心配置文件
<?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> <!--配置环境--> <environments default="mysql"> <environment id="mysql"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ssm"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!--引入配置文件,使用注解--> <mappers> <!--<mapper class="com.ywb.dao.IAccountDao"></mapper>--> <package name="com.ywb.dao"></package> </mappers> </configuration>
-
在AccountDao接口的方法上添加注解,编写SQL语句
/** * @author yang * @date 2021年08月12日 18:23 * 账户dao接口 */ public interface IAccountDao { /** * 查询所有账户 * @return */ @Select("select * from account") public List<Account> findAll(); /** * 保存账户 * @param account */ @Insert("insert into account(name,money) values(#{name},#{money})") public void saveAccount(Account account); }
-
编写测试的方法
/** * @author yang * @date 2021年08月12日 21:04 */ public class TestMybatis { /** * 测试查询 * @throws Exception */ @Test public void run1() throws Exception { //加载配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //创建SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //创建SqlSession SqlSession session = factory.openSession(); //获取到代理对象 IAccountDao accountDao = session.getMapper(IAccountDao.class); //执行方法 List<Account> accounts = accountDao.findAll(); for (Account account : accounts) { System.out.println(account); } //关闭资源 session.close(); in.close(); } /** * 测试保存 * @throws Exception */ @Test public void run2() throws Exception { Account account = new Account(); account.setName("ccc"); account.setMoney(1000d); //加载配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); //创建SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //创建SqlSession SqlSession session = factory.openSession(); //获取到代理对象 IAccountDao accountDao = session.getMapper(IAccountDao.class); //执行方法 accountDao.saveAccount(account); //提交事务 session.commit(); //关闭资源 session.close(); in.close(); } }
-
-
Spring整合MyBatis框架
-
目的:把SqlMapConfig.xml配置文件中的内容配置到applicationContext.xml配置文件中
<!--spring整合Mybatis--> <!-- 配置C3P0的连接池对象 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <!--配置SqlSessionFactory工厂--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置AccountDao接口所在的包--> <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.ywb.dao"></property> </bean>
-
在AccountDao接口中添加@Repository注解
-
在service中注入dao对象,进行测试
-
代码如下
/** * @author yang * @date 2021年08月12日 18:23 * 账户dao接口 */ @Repository("accountDao") public interface IAccountDao { /** * 查询所有账户 * @return */ @Select("select * from account") public List<Account> findAll(); /** * 保存账户 * @param account */ @Insert("insert into account(name,money) values(#{name},#{money})") public void saveAccount(Account account); }
/** * @author yang * @date 2021年08月12日 18:29 */ @Service("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; @Override public List<Account> findAll() { System.out.println("业务层:查询所有账户..."); return accountDao.findAll(); } @Override public void saveAccount(Account account) { System.out.println("业务层:保存账户..."); accountDao.saveAccount(account); } }
/** * @author yang * @date 2021年08月12日 18:31 */ @Controller @RequestMapping("/account") public class AccountController { @Autowired private IAccountService accountService; @RequestMapping("/findAll") public String findAll(Model model){ System.out.println("表现层:查询所有账户..."); //调用service方法 List<Account> accounts = accountService.findAll(); model.addAttribute("list",accounts); return "list"; } }
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>Title</title> </head> <body> <h3>查询了所有的账户信息</h3> <c:forEach items="${list}" var="account"> ${account.name} ${account.money} </c:forEach> </body> </html>
-
配置Spring的声明式事务管理
<!--配置spring框架的声明式事务管理--> <!--配置事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="find*" read-only="true"/> <tx:method name="*" isolation="DEFAULT"/> </tx:attributes> </tx:advice> <!--配置AOP增强--> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ywb.service.impl.*.*(..))"></aop:advisor> </aop:config>
-
测试保存帐户的方法
<h3>测试保存</h3> <form action="account/saveAccount" method="post"> 姓名:<input type="text" name="name"><br> 金额:<input type="text" name="money"><br> <input type="submit" value="保存"><br> </form>
@RequestMapping("/saveAccount") public void saveAccount(Account account, HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("表现层:保存账户..."); accountService.saveAccount(account); response.sendRedirect(request.getContextPath()+"/account/findAll"); return; }
-