持久层技术解决方案
mybatis的概述
注意事项
当我们遵从了第三四五点后,我们在开发中就无需再写dao的实现类
这些方法都后面都可以封装的,只是现在细一点讲底层原理,而且都写出来的话灵活性更大,参数什么的可以自由调配。
mybatis在使用代理dao的方式实现增删改查时做什么事
创建代理对象
在代理对象中调用selectList
让这两件事都串起来,每个接口和类都各司其职,以下是分析
在接口中申明方法
public interface IUserDao {
List<User> findAll();
void saveUser(User user);
void updateUser(User user);
void deleteUser(Integer userId);
User findById(Integer userId);
List<User> findByName(String userName);
Integer findTotal();
List<User> findUserByVo(QueryVo vo);
}
xml文件中写sql语句
<mapper namespace="com.itheima.dao.IUserDao">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
select>
<insert id="saveUser" parameterType="com.itheima.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
insert>
<update id="updateUser" parameterType="com.itheima.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
update>
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{uid};
delete>
<select id="findById" parameterType="INT" resultType="com.itheima.domain.User">
select * from user where id=#{uid};
select>
<select id="findByName" parameterType="String" resultType="com.itheima.domain.User">
select * from user where username like #{username};
select>
<select id="findTotal" resultType="int">
select count(id) from user;
select>
//根据queryVo中的条件查询用户
<select id="findUserByVo" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User">
select * from user where username like #{user.username};
select>
mapper>
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
OGNL表达式:Object Graphic Navigation Language(对象 图 导航 语言)
它是通过对象的取值方法来获取数据,在写法上把get给省略了
类中的写法:user.getUsername() -> user.username
mybatis中可以直接写username,因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
public class MybatisTest {
private InputStream in = null;
private SqlSession session = null;
private IUserDao userDao = null;
@Before
public void init()throws Exception{
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws Exception{
session.commit();
//6.释放资源
session.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("Mybatis lastsaveuser");
user.setAddress("湖北武汉");
user.setSex("男");
user.setBirthday(new Date());
System.out.println(user);
//5.使用代理对象添加方法
userDao.saveUser(user);
System.out.println(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(49);
user.setUsername("Mybatis");
user.setAddress("中国北京");
user.setSex("女");
user.setBirthday(new Date());
//5.使用代理对象更新方法
userDao.updateUser(user);
}
@Test
public void testDelete(){
userDao.deleteUser(49);
}
@Test
public void testFindOne(){
User user = userDao.findById(45);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> user = userDao.findByName("%王%");
for (User users : user) {
System.out.println(users);
}
}
@Test
public void testTotal(){
int count = userDao.findTotal();
System.out.println(count);
}
@Test
public void testFindByVo(){
User user = new User();
QueryVo vo = new QueryVo();
user.setUsername("%王%");
vo.setUser(user);
List<User> users = userDao.findUserByVo(vo);
for (User u : users) {
System.out.println(u);
}
}
}
注意:此时我们的数据库名称和pojo类属性名称是保持一致的,如果不一致,有两种解决方法
<resultMap id="userMap" type="com.itheima.domain.User">
<id property="userId" column="id">id>
<result property="userName" column="username">result>
<result property="userAddress" column="address">result>
<result property="userSex" column="sex">result>
<result property="userBirthday" column="birthday">result>
resultMap>
使用Mybatis完成DAO层的开发先跳
连接池:我们在实际开发中都会使用连接池,因为他可以减少我们获取连接所消耗的时间
mybatis连接池提供了3种方式的配置
位置:主配置文件SqlMapConfig.xml钟的dataSource标签,type属性就是表示采用何种连接池方式
type属性的取值:
mybatis中的事务
if标签
//查找的是已有条件的交集
<select id="findUserByCondition" parameterType="com.itheima.domain.User" resultType="com.itheima.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>
@Test
public void testFindUserByCondition(){
User user = new User();
user.setUsername("老王");
user.setSex("女");
List<User> users = userDao.findUserByCondition(user);
for (User u : users) {
System.out.println(u);
}
}
where标签
foreach和sql标签
<select id="findUserInIds" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User">
select * from user
<where>
<if test="ids!=null and ids.size()>0">
//字符串的拼接
<foreach collection="ids" open="and id in (" close=")" item="id" separator=",">
#{id}
foreach>
if>
where>
select>
@Test
public void testFindUserInIds(){
QueryVo queryVo = new QueryVo();
List<Integer> list = new ArrayList<Integer>();
list.add(42);
list.add(45);
list.add(46);
queryVo.setIds(list);
List<User> users = userDao.findUserInIds(queryVo);
for (User user : users) {
System.out.println(user);
}
}
完成account的一对一操作,当查询账户时,可以同时得到账户的所属用户信息
<resultMap id="accountUserMap" type="account"> //可以直接写account是因为在主配置文件中用typeAliases配置别名,它智能配置domain中类的别名
<id property="id" column="aid">id> //aid是a.id的别名,在sql语句中体现
<result property="uid" column="uid">result>
<result property="money" column="money">result>
<association property="user" column="uid" javaType="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
association>
resultMap>
完成user的一对多查询操作,查询用户时,可以同时得到用户下所包含的账户信息
<mapper namespace="com.itheima.dao.IUserDao">
<resultMap id="userAccountMap" type="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
<collection property="accounts" ofType="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 a.`UID` = u.id;
select>
mapper>
@Test
public void testFindAll(){
List users = userDao.findAll();
for (User user : users) {
System.out.println(user);
System.out.println(user.getAccounts());
}
}
多对多操作
建立两张表:用户表,角色表;让两张表具有多对多的关系,需要使用中间表,中间表包含各种的主键,在中间表是外键
建立两个实体类:用户实体类和角色实体类,让两个实体类能体现出来多对多的关系,各自包含对方的一个集合引用
建立两个映射配置文件:用户的和角色的配置文件(以角色配置文件举例)
<mapper namespace="com.itheima.dao.IRoleDao">
<resultMap id="roleMap" type="role">
<id property="roleId" column="id">id>
<result property="roleName" column="role_name">result>
<result property="roleDesc" column="role_desc">result>
<collection property="users" ofType="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
collection>
resultMap>
<select id="findAll" resultMap="roleMap">
SELECT * FROM role r
LEFT OUTER JOIN user_role ur
ON r.`ID`=ur.`RID`
LEFT OUTER JOIN USER u
ON u.`id`=ur.`UID`;
select>
mapper>
实现配置:当我们查询用户时,可以同时得到用户所包含的角色信息,查询角色时,能同时得到角色所属的用户信息。
@Test
public void testFindAll(){
List roles = roleDao.findAll();
for (Role role : roles) {
System.out.println(role);
System.out.println(role.getUsers());
}
}
总结:多表操作的步骤都类似,按照流程走就行了
是SUN公司推出的一套规范,属于JavaEE技术之一,目的是模范windows系统的注册表
注册表里面为什么同一个key名称可以有不同的value,因为这些key处在不同的目录中,实际上不是同一个key
概念:在真正使用数据时才发起查询,按需加载(懒加载)
什么时候使用:
如何使用:
mybatis第三天实现多表操作时,我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要 是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能
一对一实现延迟加载,找到账户表以及对应的用户
在之前一对一工程基础上改造
配置account的映射配置文件,user的配置文件不变
<mapper namespace="com.itheima.dao.IAccountDao">
<resultMap id="accountUserMap" type="account">
<id property="id" column="id">id>
<result property="uid" column="uid">result>
<result property="money" column="money">result>
<association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById">//uid不能省略,要传到findById里面
association>
resultMap>
<select id="findAll" resultMap="accountUserMap">
select * from account;
select>
mapper>
<mapper namespace="com.itheima.dao.IUserDao">
<resultMap id="userAccountMap" type="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
<collection property="accounts" ofType="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>
<select id="findById" resultType="user">
SELECT * FROM user where id = #{uid}
select>
mapper>
主配置文件添加setting配置,打开延迟加载开关
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false">setting>
settings>
一对多实现延迟加载
概念:存在于内存中的临时数据
为什么使用:减少和数据库的交互次数
什么时候使用:
一级缓存和二级缓存
一级缓存:指的是Mybatis中SqlSession对象的缓存,当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。当SqlSession对象消失时,mybatis的以及缓存也就消失了
sqlSession = factory.close();
//再次获取SqlSession对象
sqlSession = factory.openSession();
userDao = sqlSession.getMapper(IUserDao.class);
sqlSession.clearCache();//此方法也可以清空缓存
userDao = sqlSession.getMapper(IUserDao.class);
二级缓存:指的是Mybatis中SqlSessionFacrory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
使用步骤
为什么注解开发可以代替映射配置文件
建立实体类属性和数据库列的对应关系,用Results和ResultMap注解,如下
public interface IUserDao {
@Select("select * from user")
@Results(id = "userMap",value = {
@Result(id = true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
})
List<User> findAll();
@Select("select * from user where id=#{id}")
@ResultMap("userMap")
User findById(Integer userId);
@Select("select * from user where username like '%${value}%' ")
@ResultMap("userMap")
List<User> findByName(String userName);
}
注解开发一对一的查询配置
建立account表的javaBean并且添加private User user;
注解
public interface IAccountDao {
@Select("select * from account")
@Results(id = "accountMap",value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
//下面这行是精华,并且同时实现了延迟加载
@Result(property = "user",column = "uid",one = @One(select = "com.itheima.dao.IUserDao.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();
}
注解开发一对多的查询配置
在user表中添加private List accounts;
IAccountDao中添加查询方法
@Select("select * from account where uid=#{userId}")
List findByUid(Integer userId);
IUserDao注解加一行
@Result(property = "accounts",column = "id",many = @Many(select = "com.itheima.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
使用二级缓存
概念:Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术
优点:方便解耦,简化开发、AOP编程的支持 、声明式事务的支持 、方便程序的测试 、方便集成各种优秀框架、降低 JavaEE API的使用难度 、Java源码是经典学习范例
体系结构
耦合:程序间的依赖关系,包括:类之间的依赖,方法间的依赖
解耦:降低程序间的依赖关系
例子:
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());没有导入jar包编译器就会报错
Class.forName("com.mysql.jdbc.Driver");//写死了,还是不太行
使用工厂模式解耦
创建一个Bean对象的工厂,它就是创建我们的service和dao对象的
public class BeanFactory {
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的名称获取对象
* @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;
}
}
bean.properties配置文件内容
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
更改之前通过new创建下层示例
//IAccountService as = new AccountServiceImpl();废弃
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
//private IAccountDao accountDao = new AccountDaoImpl();废弃
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
工厂模式解耦的升级版
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个Key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
概念:把创建对象的权力交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入DI和依赖查找DL,之前用的BeanFactory类就使用了这个思想,可以消减计算机程序的耦合。它只能解决程序间的依赖关系,其他什么都做不了
配置文件bean.xml
客户端实现
public class Client {
public static void main(String[] args) {
/**
* 获取spring的Ioc核心容器,并根据id获取对象
*
* ApplicationContext的三个常用实现类:
* ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
* FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
* AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是下次的内容
*
* 核心容器的两个接口引发出的问题:
* ApplicationContext: 单例对象使用
* 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象
* BeanFactory: 多例对象使用
* 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象
*/
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
IAccountService as = (IAccountService)ac.getBean("acountService");
IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
创建bean的三种方式
使用默认构造函数创建:在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创造。
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
bean的作用范围调整:bean标签的scope属性:
bean的生命周期
单例对象
多例对象
xml中的标签定义
//init和destroy方法已经在实现类中定义好了
概念:在当前类需要用到其他类的对象,由spring为我们提供,以后依赖关系的管理都交给spring来维护,我们只需要在配置文件中说明。依赖关系的维护就称之为依赖注入
能注入的数据:
基本类型和String
其它bean类型(在配置文件中或者注解配置过的bean,就是使用ref的这个)
复杂类型/集合类型
用于给List结构集合注入的标签:list array set
用于给Map结构集合注入的标签:map props
结构相同,标签可以互换,如
AAA
BBB
CCC
注入的方式:
使用构造函数提供(把数据传入构造方法的参数)
使用方法
//String类型
//Interger类型
//Date类型
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:改变了bean对象的实例化方式,是我们在创建对象时如果用不到这些数据也必须提供
使用set方法提供(get方法没必要) 更常用
使用方法
scope=" init-method="" destroy-method="">
用于创建对象的:作用和XML配置文件中编写一个标签实现的功能是一样的
在要被创建的对象的类上写上Component注解
//作用:用于把当前类对象存入spring容器中
//属性:value:用于指定bean的id。不写的时候默认是当前类名,且首字母改小写
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public AccountServiceImpl() {
System.out.println("对象创建了");
}
public void saveAccount() {
accountDao.saveAccount();
}
}
现在还不能扫描找到,需要在官网找到xml的配置代码
//这行自己配
现在在客户端类中可以正常使用了,控制台输出:“对象创建了”
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
System.out.println(as);
}
}
其他用于创建对象的注解
Controller:一般用在表现层
Service:一般用在业务层
Repository:一般用在持久层
以上三个注解的作用和属性于Component一模一样,是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
用于注入数据的:作用就和xml配置文件中的bean标签中写一个标签的作用是一样的
Autowired:
Qualifier:
Resource
用于改变作用范围的:作用就和在bean标签中使用scope属性实现的功能是一样的
和生命周期相关(了解):作用就和在bean标签中使用init-method和destroy-method的作用是一样的
xml文件配置,基本用到了前面的内容
//如何创建bean对象
//如何注入数据,注入的两种类型之其它bean类型
//注入数据的两种方式之set方法注入
注入数据的两种方式之构造函数注入
//注入的两种类型之基本类型和String
在每个测试方法前面加上获取bean对象,其他操作同之前不变,如:
@Test
public void testFindAll(){
//之后下面这两行可以在spring整合junit中解决
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
List accounts = as.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
用注解改造
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private AccountDaoImpl accountDao;
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
并且在bean对象中添加扫描属性以及修改成context头
用配置类代替bean.xml文件(这节课有点偷懒了)
package config;
/**
* 该类是一个配置类,它的作用和bean.xml是一样的
* spring中的新注解
* Configuration
* 作用:指定当前类是一个配置类
* 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
* ComponentScan
* 作用:用于通过注解指定spring在创建容器时要扫描的包
* 属性:
* value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
* 我们使用此注解就等同于在xml中配置了:
*
* Bean
* 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
* 属性:
* name:用于指定bean的id。当不写时,默认值是当前方法的名称
* 细节:
* 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
* 查找的方式和Autowired注解的作用是一样的
* Import
* 作用:用于导入其他的配置类
* 属性:
* value:用于指定其他配置类的字节码。
* 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
* PropertySource
* 作用:用于指定properties文件的位置
* 属性:
* value:指定文件的名称和路径。
* 关键字:classpath,表示类路径下
*/
//@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
//这是共同的配置类
}
子配置类(针对一些特定的配置)
public class JdbcConfig {
//读取properties配置文件内容的一个好方法
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
//Qualifier可以用在参数位置上,选择对象具体的哪个实现类
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
//不用写死了
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
//一个对象有多个实现类的情况
@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
使用xml配置还是注解配置
spring整合junit
整合的思路
整合的步骤
/**
* 使用Junit单元测试:测试我们的配置
* Spring整合junit的配置
* 1、导入spring整合junit的jar(坐标)
* 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
* @Runwith
* 3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
* @ContextConfiguration
* locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
* classes:指定注解类所在地位置
*
* 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//3.执行方法
List accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("test anno");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//3.执行方法
as.deleteAccount(4);
}
}
问题:转账操作需要添加事务,在业务层实现每个功能都要放在事务的生命周期中,而且加上事务类还有复杂的bean.xml配置,耦合度非常高
使用动态代理可以解决,回顾基于接口的动态代理
/**
* 一个生产者
*/
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);
}
}
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
*/
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
基于子类的动态代理
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行该对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
使用基于接口的动态代理给原Service方法增加事务
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
bean.xml中配置注入
概念:它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的技术上,对我们的已有方法进行增强。可以减少重复代码,提高开发效率,而且维护方便
关于代理的选择:spring中会根据目标类是否实现了接口来觉得采用哪种动态代理的方式
相关术语(概念性的,有利于后期自学)
Joinpoint(连接点):是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。例如我们在业务层的方法都是连接点
Pointcut(切入点):是指我们要对哪些Joinpoint进行拦截的定义。切入点就是被增强的方法,是连接点的子集。例如对账户的增删改查
Advice(通知/增强):通知是指拦截到Joinpoint之后所要做的事情
Introduction(引介):在不修改类代码的前提下,可以在运行期为类动态地添加一些方法或Field
Target(目标对象):被代理对象
Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入
Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
Aspect(切面):是切入点和通知(引介)的结合。仔细理解
学习Spring中的AOP要明确的事
开发阶段(我们要做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
运行阶段(Spring框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
实战演示
编写Service方法,和一个用于记录日志的前置通知方法,对Service中的saveAccount方法进行增强
基于XML配置
四种常用通知类型+切入点表达式+环绕通知的xml配置
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 aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
先修改bean.xml文件内容
修改通知方法Logger
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.itheima.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方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
如果想彻底删除bean.xml文件
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy //添加这个
public class SpringConfiguration {
}
Factory是添加事务的工厂类,可以用aop代替
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
bean.xml中导入aop的约束并添加配置
改造成注解配置
简介
基本使用
导包 jdbc和tx(事务相关)
先用老方法使用
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("root");
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(ds);
jt.execute("insert into account(name,money)values('ccc',100)");
}
使用IOC解耦以及属性注入
bean.xml文件
main文件
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
jt.execute("insert into account(name,money)values('ddd',222)");
CRUD
代码部分
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
// jt.execute("insert into account(name,money)values('ddd',222)");
//增加一行数据
jt.update("insert into account(name,money)values(?,?)","eee",111f);
//删除一行数据
jt.update("delete from account where id = ?",3);
//修改一条数据
jt.update("update account set name=?,money=? where id=?","qwe",1234,4);
//查询所有数据
//使用BeanPropertyRowMapper就可以不用自己实现RowMapper接口了
List accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper(Account.class), 500f);
for (Account account : accounts) {
System.out.println(account);
}
//查询一个数据
List accounts = jt.query("select * from account where id = ?", new BeanPropertyRowMapper(Account.class), 1);
System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));
//查询一行一列
Integer count = jt.queryForObject("select count(*) from account where money > ?", Integer.class, 500f);
System.out.println(count);
}
分析方法
在Dao中的使用
创建接口和实现类,并且注入jdbcTemplate
//Dao层中下面两行可以抽取出来,继承spring中的JdbcDaoSupport(老师又默默地带我们手撕源码)。但是就不能在下面这行加@Autowired注解了
private JdbcTemplate jt;
public void setJt(JdbcTemplate jt) {
this.jt = jt;
}
目的:使用spring代替之前自己定义的事务方法
@Component("txManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 开启事务
*/
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();
}
}
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();
this.beginTransaction();
rtValue = pjp.proceed(args);
this.commit();
return rtValue;
}catch (Throwable e){
this.rollback();
throw new RuntimeException(e);
}finally {
this.release();
}
}
}
基于xml的声明式事务控制
在官方文档中找到并引入tx约束
基于注解的声明式事务控制
加入xmlns:context的约束
配置spring创建容器时要扫描的包
添加其他service和dao注解同上
如下
基于纯注解的声明式事务控制
/**
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}
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;
}
}
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=root
@ContextConfiguration(classes= SpringConfiguration.class)
三层架构
优势
清晰的角色划分: 前端控制器(DispatcherServlet) 请求到处理器映射(HandlerMapping) 处理器适配器(HandlerAdapter) 视图解析器(ViewResolver) 处理器或页面控制器(Controller) 验证器( Validator) 命令对象(Command 请求参数绑定到的对象就叫命令对象) 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
可定制性,HandlerMapping、ViewResolver 等能够非常简单的定制。
功能强大的数据验证、格式化、绑定机制。
利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
强大的 JSP 标签库,使 JSP 编写更容易。
………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。
和Struts2的优略分析
共同点:它们都是表现层框架,都是基于 MVC 模型编写的。
它们的底层都离不开原始 ServletAPI。
它们处理请求的机制都是一个核心控制器。
区别:Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所 以 Spring MVC 会稍微比 Struts2 快些。
Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便 (JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注 解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提 升,尤其是 struts2 的表单标签,远没有 html 执行效率高。
通过SpringMVC完成一个页面的跳转。分析
在index.jsp中写个跳转的超链接
配置web.xml
Archetype Created Web Application
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
dispatcherServlet
/
配置springmvc.xml
编写controller代码
@Controller //这个不是MVC的注解,是扫描的注解
public class HelloController {
@RequestMapping(path = "/hello")
public String sayHello(){
System.out.println("Hello StringMVC");
return "success";
}
}
在跳转到的success.jsp中写出访问成功
项目结构
大概的流程
当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象, 就会加载springmvc.xml配置文件
开启了注解扫描,那么HelloController对象就会被创建
从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解 找到执行的具体方法
根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
Tomcat服务器渲染页面,做出响应
详细的流程以及组件分析
前端控制器(DispatcherServlet)
处理器映射器(HandlerMapping)
处理器(Handler)
处理器适配器(HandlAdapter)
视图解析器(View Resolver)
视图(View)
RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
RequestMapping注解可以作用在方法和类上
RequestMapping的属性
入门程序
新建param.jsp
参数绑定入门程序
测试参数
Controller可以通过参数接收
@Controller
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/testParam")
public String testParam(String username,String password){
System.out.println("用户名是:"+ username);
System.out.println("密码是:"+ password);
return "success";
}
}
绑定实体类型
页面改成表单提交
新建Account类和User类,User类是Accont类中的属性
controller
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println(account);
return "success";
}
解决中文乱码问题
以前通过配置request.setCharacterEnCoding方法解决
现在通过在web.xml中配置过滤器
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
characterEncodingFilter
/*
绑定集合类型
Account实体类中添加List和Map集合的属性
表单的值修改成如下格式
用户姓名:
用户年龄:
用户姓名:
用户年龄:
自定义类型转换器
问题:2020-11-11识别不了,MVC只能自动转换2020/11/11类型的
创建一个类实现Converter接口
public class StringToDateConverter implements Converter{
/**
* String source 传入进来字符串
* @param source
* @return
*/
public Date convert(String source) {
// 判断
if(source == null){
throw new RuntimeException("请您传入数据");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
// 把字符串转换日期
return df.parse(source);
} catch (Exception e) {
throw new RuntimeException("数据类型转换出现错误");
}
}
}
配置springmvc.xml
获取Servlet原生的API
直接在Controller的方法里面传参数就行了
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了...");
System.out.println(request);
HttpSession session = request.getSession();
System.out.println(session);
ServletContext servletContext = session.getServletContext();
System.out.println(servletContext);
System.out.println(response);
return "success";
}
RequestParam
问题:之前的请求中参数是什么名称,控制器中的形式参数也必须是这个名称,如果不是,就需要使用这个参数
属性
用在参数的前面
public String testRequestParam(@RequestParam(name="name") String username)
RequestBody
PathVariable
作用:用于绑定url中的占位符。例如:请求url中/delete/{id},这个{id}就是url占位符。url支持占位符是spring3.0之后加入的。是springmvc支持rest风格URL的一个重要标志
属性:
restful编程风格
在jsp和controller中的使用
RequestParam
@RequestMapping(path = "/testPathVariable/{sid}")
public String testPathVariable(@PathVariable("sid") String id){
System.out.println(id); //输出10
return "success";
}
HiddentHttpMethodFilter
RequestHeader
CookieValue
ModelAttibute
作用:它可以用于修饰方法和参数,出现在方法上,表示当前的方法会在控制器的方法执行之前先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法
属性:value:用于获取数据的key,key可以是pojo的属性名称,也可以是map结构的key
应用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据
例子:用户有uname,age,date三个字段,但是表单只提交uname和age,如果想让date保持数据库原来的值可以使用这个注解
有返回值
@ModelAttribute
public void showUser(String name){
System.out.println("showUser执行了");
User user = new User();
user.setUname(name);
user.setAge(20);
user.setDate(new Date());
map.put("abc",user);
}
@ModelAttribute
public User showUser(String name){
System.out.println("showUser执行了");
User user = new User();
user.setUname(name);
user.setAge(20);
user.setDate(new Date());
return user;
}
有返回值
@RequestMapping(path = "/testModelAttribute")
public String testModelAttribute(@ModelAttribute("abc") User user){
System.out.println(user);
return "success";
}
@ModelAttribute
public void showUser(String name, Map map){
System.out.println("showUser执行了");
User user = new User();
user.setUname(name);
user.setAge(20);
user.setDate(new Date());
map.put("abc",user);
}
SessionAttribute
作用:用于多次执行控制器方法间的参数共享
属性:
例子
创建三个超链接分别指向三个controller
controller
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"msg"}) //只能作用到类上
public class AnnoController {
@RequestMapping(path = "/testSessionAttribute")
public String testSessionAttribute(Model model){
model.addAttribute("msg","美美");
return "success";
}
@RequestMapping(path = "/getSessionAttribute")
public String getSessionAttribute(ModelMap modelMap){
modelMap.get("msg");
return "success";
}
@RequestMapping(path = "/delSessionAttribute")
public String delSessionAttribute(SessionStatus status){
status.setComplete();
return "success";
}
}
success.jsp
${msg}
${sessionScope}
返回值是String类型
将查询出的对象存入request域中
@RequestMapping("/testString")
public String testString(Model model){
System.out.println("testString方法执行了。。。");
//模拟从数据库中查询出User对象
User user = new User();
user.setUsername("张三");
user.setPassword("123");
user.setAge(20);
model.addAttribute("user",user);
return "success";
}
再在前端通过el表达式显示
${user.username}
${user.password}
返回值是void类型
默认会发送到WEB-INF目录下对应访问地址的jsp文件
可以按照传统方法指定显示路径
@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().write("你好");
return;
}
返回值是ModelAndView类型
与返回值是String类型差不多,那个其实就是以这个作为底层原理
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){ //这个接口作为形式参数是怎么发挥作用的
System.out.println("testString方法执行了。。。");
ModelAndView mv = new ModelAndView();
//模拟从数据库中查询出User对象
User user = new User();
user.setUsername("李四");
user.setPassword("321");
user.setAge(20);
mv.addObject("user",user);
mv.setViewName("success");//可以使用视图解析器
return mv;
}
使用使用forward和redirect进行页面跳转
使用关键字的方法进行转发或重定向
@RequestMapping("/testForwardAndRedirect")
public String testForwardAndRedirect(){ //这个接口作为形式参数是怎么发挥作用的
System.out.println("testForwardAndRedirect。。。");
//请求转发
// return "forward:/WEB-INF/pages/success.jsp";
//重定向
return "redirect:/index.jsp"; //该jsp文件放在webapp目录下
}
过滤静态资源
引入jQuery,并绑定一个点击事件
Title
在springmvc.xml中设置静态资源不过滤
响应json数据之发送ajax的请求
用如下代码替换上面的alert
$.ajax({
url:"user/testAjax",
contentType:"application/json;charset=UTF-8",
data:'{"username":"hehe","password":"123","age":30}',
dataType:"json",
type:"post",
success:function (data) {
}
});
模拟异步请求响应
@RequestMapping("/testAjax")
public void testAjax(@RequestBody String body){
System.out.println("testAjax。。。");
System.out.println(body);
}
显示结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tiicAEH5-1605952528223)(C:\Users\chenzhijian\AppData\Roaming\Typora\typora-user-images\image-20201113162537554.png)]
响应json数据之响应json格式数据
导入jackson的jar包,使得json字符串和javaBean对象可以互相转换
将前端传过来的json数据包装到user对象中
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user){
System.out.println("testAjax。。。");
System.out.println(user);
user.setUsername("haha");
user.setAge(40);
return user;
}
前端显示后端更改的数据
success:function (data) {
alert(data);
alert(data.username);
alert(data.password);
alert(data.age);
}
原理分析
代码实现
引入fileupload和io的pom依赖
配置解析器对象
创建表单
controller代码
@RequestMapping("/fileupload")
public String fileUpload(HttpServletRequest request, MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样
System.out.println("文件上传...");
//上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
//判断该路径是否存在
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
//设置唯一文件名
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid+"_"+filename;
//上传文件
upload.transferTo(new File(path,filename));
return "success";
}
分析
代码实现
引入jersey的pom依赖
创建表单
代码实现
@RequestMapping("/fileupload2")
public String fileUpload2(MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样
System.out.println("服务器文件上传...");
//上传的位置
String path = "http://localhost:9090/uploads/";
//设置唯一文件名
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid+"_"+filename;
//创建客户端对象
Client client = Client.create();
//建立连接
WebResource webResource = client.resource(path+filename);
//上传文件
webResource.put(upload.getBytes());
return "success";
}
出现403forbidden
更改tomcat配置,详细见https://blog.csdn.net/Dawn510/article/details/103915414
Controller调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet找异常处理器进行异常的处理
controller
@RequestMapping("/testException")
public String fileException() throws SysException {
System.out.println("Exception...");
try {
int i = 10/0;
} catch (Exception e) {
e.printStackTrace();
throw new SysException("查询所有用户出现了错误。。。");
}
return "success";
}
编写自定义异常类
public class SysException extends Exception{
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public SysException(String message) {
this.message = message;
}
}
编写异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
SysException e = null;
if(ex instanceof SysException){
e = (SysException) ex;
}else {
e = new SysException("系统正在维护");
}
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e.getMessage());
mv.setViewName("error");
return mv;
}
}
编写error页面
配置异常处理器
拦截器的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
与过滤器的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦 截的。
它也是 AOP 思想的具体应用。 我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。
拦截器类
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("拦截器方法执行了。。。");
return true;
}
}
springmvc.bean中配置拦截器
jsp中输出
访问成功
<% System.out.println("success.jsp执行了..."); %>
结果 拦截器->controller->jsp
三种处理方法的总结:
如果配多个拦截器
在resources资源文件中创建spring的配置文件以及引入log4j
在业务层添加@Service(“accountService”)注解
在测试类中测试成功
@Test
public void run1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
AccountService as = (AccountService) ac.getBean("accountService");
as.findAll();
}
先搭建SpringMVC的环境,测试能不能独立运行
配置web.xml
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
dispatcherServlet
/
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
characterEncodingFilter
/*
配置springmvc.xml
index.jsp页面
测试
Controller
@Controller("accountController")
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/findAll")
public String testFindAll(){
System.out.println("表现层:找到所有列表");
return "list"; //list.jsp输出文字提示信息
}
}
整合SpringMVC
先搭建Mybatis环境
创建主配置文件sqlMapConfig.xml
用注解写两个方法的sql语句
测试查询
@Test
public void run1() throws Exception {
// 加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 创建工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建sqlSession对象
SqlSession session = factory.openSession();
// 获取代理对象
AccountDao dao = session.getMapper(AccountDao.class);
// 调用查询的方法
List list = dao.findAll();
for (Account account : list) {
System.out.println(account);
}
// 释放资源
session.close();
inputStream.close();
}
测试保存(增删改需要自己提交事务)
@Test
public void run2() throws Exception {
Account account = new Account();
account.setName("熊大");
account.setMoney(300d);
// 加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 创建工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建sqlSession对象
SqlSession session = factory.openSession();
// 获取代理对象
AccountDao dao = session.getMapper(AccountDao.class);
// 调用查询的方法
dao.saveAccount(account);
//提交事务
session.commit();
// 释放资源
session.close();
inputStream.close();
}
整合MyBatis框架
将MyBatis配置类中的内容移到spring的配置类applicationContext.xml中,进入ioc容器,完成后可以删除sqlMapConfig.xml
在Service中注入Dao,并且调用其方法,返回
@Override
public List findAll() {
System.out.println("业务层:查询所有的账户信息");
return accountDao.findAll();
}
Controller存入域对象中,让页面能够读取
@RequestMapping("/findAll")
public String findAll(Model model){
System.out.println("表现层:查询所有的账户信息");
List list = accountService.findAll();
model.addAttribute("list",list);
return "list";
}
jsp页面显示出数据
${account.name}
配置事务,写保存账户方法
配置Spring框架声明式事务管理
//尽可能避开整体扫描
写一个提交表单
Controller调Service,Service调Dao
@RequestMapping("/save")
public void save(Account account, HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("表现层:保存账户信息");
accountService.saveAccount(account);
response.sendRedirect(request.getContextPath()+"/account/findAll");
return;
}
完成
持久层技术解决方案
mybatis的概述
注意事项
当我们遵从了第三四五点后,我们在开发中就无需再写dao的实现类
这些方法都后面都可以封装的,只是现在细一点讲底层原理,而且都写出来的话灵活性更大,参数什么的可以自由调配。
mybatis在使用代理dao的方式实现增删改查时做什么事
创建代理对象
在代理对象中调用selectList
让这两件事都串起来,每个接口和类都各司其职,以下是分析
在接口中申明方法
public interface IUserDao {
List<User> findAll();
void saveUser(User user);
void updateUser(User user);
void deleteUser(Integer userId);
User findById(Integer userId);
List<User> findByName(String userName);
Integer findTotal();
List<User> findUserByVo(QueryVo vo);
}
xml文件中写sql语句
<mapper namespace="com.itheima.dao.IUserDao">
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
select>
<insert id="saveUser" parameterType="com.itheima.domain.User">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
selectKey>
insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday});
insert>
<update id="updateUser" parameterType="com.itheima.domain.User">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
update>
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id=#{uid};
delete>
<select id="findById" parameterType="INT" resultType="com.itheima.domain.User">
select * from user where id=#{uid};
select>
<select id="findByName" parameterType="String" resultType="com.itheima.domain.User">
select * from user where username like #{username};
select>
<select id="findTotal" resultType="int">
select count(id) from user;
select>
//根据queryVo中的条件查询用户
<select id="findUserByVo" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User">
select * from user where username like #{user.username};
select>
mapper>
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
OGNL表达式:Object Graphic Navigation Language(对象 图 导航 语言)
它是通过对象的取值方法来获取数据,在写法上把get给省略了
类中的写法:user.getUsername() -> user.username
mybatis中可以直接写username,因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名
public class MybatisTest {
private InputStream in = null;
private SqlSession session = null;
private IUserDao userDao = null;
@Before
public void init()throws Exception{
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象
session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象
userDao = session.getMapper(IUserDao.class);
}
@After
public void destroy() throws Exception{
session.commit();
//6.释放资源
session.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("Mybatis lastsaveuser");
user.setAddress("湖北武汉");
user.setSex("男");
user.setBirthday(new Date());
System.out.println(user);
//5.使用代理对象添加方法
userDao.saveUser(user);
System.out.println(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(49);
user.setUsername("Mybatis");
user.setAddress("中国北京");
user.setSex("女");
user.setBirthday(new Date());
//5.使用代理对象更新方法
userDao.updateUser(user);
}
@Test
public void testDelete(){
userDao.deleteUser(49);
}
@Test
public void testFindOne(){
User user = userDao.findById(45);
System.out.println(user);
}
@Test
public void testFindByName(){
List<User> user = userDao.findByName("%王%");
for (User users : user) {
System.out.println(users);
}
}
@Test
public void testTotal(){
int count = userDao.findTotal();
System.out.println(count);
}
@Test
public void testFindByVo(){
User user = new User();
QueryVo vo = new QueryVo();
user.setUsername("%王%");
vo.setUser(user);
List<User> users = userDao.findUserByVo(vo);
for (User u : users) {
System.out.println(u);
}
}
}
注意:此时我们的数据库名称和pojo类属性名称是保持一致的,如果不一致,有两种解决方法
<resultMap id="userMap" type="com.itheima.domain.User">
<id property="userId" column="id">id>
<result property="userName" column="username">result>
<result property="userAddress" column="address">result>
<result property="userSex" column="sex">result>
<result property="userBirthday" column="birthday">result>
resultMap>
使用Mybatis完成DAO层的开发先跳
连接池:我们在实际开发中都会使用连接池,因为他可以减少我们获取连接所消耗的时间
mybatis连接池提供了3种方式的配置
位置:主配置文件SqlMapConfig.xml钟的dataSource标签,type属性就是表示采用何种连接池方式
type属性的取值:
mybatis中的事务
if标签
//查找的是已有条件的交集
<select id="findUserByCondition" parameterType="com.itheima.domain.User" resultType="com.itheima.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>
@Test
public void testFindUserByCondition(){
User user = new User();
user.setUsername("老王");
user.setSex("女");
List<User> users = userDao.findUserByCondition(user);
for (User u : users) {
System.out.println(u);
}
}
where标签
foreach和sql标签
<select id="findUserInIds" parameterType="com.itheima.domain.QueryVo" resultType="com.itheima.domain.User">
select * from user
<where>
<if test="ids!=null and ids.size()>0">
//字符串的拼接
<foreach collection="ids" open="and id in (" close=")" item="id" separator=",">
#{id}
foreach>
if>
where>
select>
@Test
public void testFindUserInIds(){
QueryVo queryVo = new QueryVo();
List<Integer> list = new ArrayList<Integer>();
list.add(42);
list.add(45);
list.add(46);
queryVo.setIds(list);
List<User> users = userDao.findUserInIds(queryVo);
for (User user : users) {
System.out.println(user);
}
}
完成account的一对一操作,当查询账户时,可以同时得到账户的所属用户信息
<resultMap id="accountUserMap" type="account"> //可以直接写account是因为在主配置文件中用typeAliases配置别名,它智能配置domain中类的别名
<id property="id" column="aid">id> //aid是a.id的别名,在sql语句中体现
<result property="uid" column="uid">result>
<result property="money" column="money">result>
<association property="user" column="uid" javaType="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
association>
resultMap>
完成user的一对多查询操作,查询用户时,可以同时得到用户下所包含的账户信息
<mapper namespace="com.itheima.dao.IUserDao">
<resultMap id="userAccountMap" type="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
<collection property="accounts" ofType="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 a.`UID` = u.id;
select>
mapper>
@Test
public void testFindAll(){
List users = userDao.findAll();
for (User user : users) {
System.out.println(user);
System.out.println(user.getAccounts());
}
}
多对多操作
建立两张表:用户表,角色表;让两张表具有多对多的关系,需要使用中间表,中间表包含各种的主键,在中间表是外键
建立两个实体类:用户实体类和角色实体类,让两个实体类能体现出来多对多的关系,各自包含对方的一个集合引用
建立两个映射配置文件:用户的和角色的配置文件(以角色配置文件举例)
<mapper namespace="com.itheima.dao.IRoleDao">
<resultMap id="roleMap" type="role">
<id property="roleId" column="id">id>
<result property="roleName" column="role_name">result>
<result property="roleDesc" column="role_desc">result>
<collection property="users" ofType="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
collection>
resultMap>
<select id="findAll" resultMap="roleMap">
SELECT * FROM role r
LEFT OUTER JOIN user_role ur
ON r.`ID`=ur.`RID`
LEFT OUTER JOIN USER u
ON u.`id`=ur.`UID`;
select>
mapper>
实现配置:当我们查询用户时,可以同时得到用户所包含的角色信息,查询角色时,能同时得到角色所属的用户信息。
@Test
public void testFindAll(){
List roles = roleDao.findAll();
for (Role role : roles) {
System.out.println(role);
System.out.println(role.getUsers());
}
}
总结:多表操作的步骤都类似,按照流程走就行了
是SUN公司推出的一套规范,属于JavaEE技术之一,目的是模范windows系统的注册表
注册表里面为什么同一个key名称可以有不同的value,因为这些key处在不同的目录中,实际上不是同一个key
概念:在真正使用数据时才发起查询,按需加载(懒加载)
什么时候使用:
如何使用:
mybatis第三天实现多表操作时,我们使用了resultMap来实现一对一,一对多,多对多关系的操作。主要 是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能
一对一实现延迟加载,找到账户表以及对应的用户
在之前一对一工程基础上改造
配置account的映射配置文件,user的配置文件不变
<mapper namespace="com.itheima.dao.IAccountDao">
<resultMap id="accountUserMap" type="account">
<id property="id" column="id">id>
<result property="uid" column="uid">result>
<result property="money" column="money">result>
<association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById">//uid不能省略,要传到findById里面
association>
resultMap>
<select id="findAll" resultMap="accountUserMap">
select * from account;
select>
mapper>
<mapper namespace="com.itheima.dao.IUserDao">
<resultMap id="userAccountMap" type="user">
<id property="id" column="id">id>
<result property="username" column="username">result>
<result property="address" column="address">result>
<result property="sex" column="sex">result>
<result property="birthday" column="birthday">result>
<collection property="accounts" ofType="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>
<select id="findById" resultType="user">
SELECT * FROM user where id = #{uid}
select>
mapper>
主配置文件添加setting配置,打开延迟加载开关
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false">setting>
settings>
一对多实现延迟加载
概念:存在于内存中的临时数据
为什么使用:减少和数据库的交互次数
什么时候使用:
一级缓存和二级缓存
一级缓存:指的是Mybatis中SqlSession对象的缓存,当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供的一块区域中,该区域的结构是一个Map。当SqlSession对象消失时,mybatis的以及缓存也就消失了
sqlSession = factory.close();
//再次获取SqlSession对象
sqlSession = factory.openSession();
userDao = sqlSession.getMapper(IUserDao.class);
sqlSession.clearCache();//此方法也可以清空缓存
userDao = sqlSession.getMapper(IUserDao.class);
二级缓存:指的是Mybatis中SqlSessionFacrory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
使用步骤
为什么注解开发可以代替映射配置文件
建立实体类属性和数据库列的对应关系,用Results和ResultMap注解,如下
public interface IUserDao {
@Select("select * from user")
@Results(id = "userMap",value = {
@Result(id = true,column = "id",property = "userId"),
@Result(column = "username",property = "userName"),
@Result(column = "address",property = "userAddress"),
@Result(column = "sex",property = "userSex"),
@Result(column = "birthday",property = "userBirthday"),
})
List<User> findAll();
@Select("select * from user where id=#{id}")
@ResultMap("userMap")
User findById(Integer userId);
@Select("select * from user where username like '%${value}%' ")
@ResultMap("userMap")
List<User> findByName(String userName);
}
注解开发一对一的查询配置
建立account表的javaBean并且添加private User user;
注解
public interface IAccountDao {
@Select("select * from account")
@Results(id = "accountMap",value = {
@Result(id = true,column = "id",property = "id"),
@Result(column = "uid",property = "uid"),
@Result(column = "money",property = "money"),
//下面这行是精华,并且同时实现了延迟加载
@Result(property = "user",column = "uid",one = @One(select = "com.itheima.dao.IUserDao.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();
}
注解开发一对多的查询配置
在user表中添加private List accounts;
IAccountDao中添加查询方法
@Select("select * from account where uid=#{userId}")
List findByUid(Integer userId);
IUserDao注解加一行
@Result(property = "accounts",column = "id",many = @Many(select = "com.itheima.dao.IAccountDao.findByUid",fetchType = FetchType.LAZY))
使用二级缓存
概念:Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术
优点:方便解耦,简化开发、AOP编程的支持 、声明式事务的支持 、方便程序的测试 、方便集成各种优秀框架、降低 JavaEE API的使用难度 、Java源码是经典学习范例
体系结构
耦合:程序间的依赖关系,包括:类之间的依赖,方法间的依赖
解耦:降低程序间的依赖关系
例子:
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());没有导入jar包编译器就会报错
Class.forName("com.mysql.jdbc.Driver");//写死了,还是不太行
使用工厂模式解耦
创建一个Bean对象的工厂,它就是创建我们的service和dao对象的
public class BeanFactory {
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的名称获取对象
* @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;
}
}
bean.properties配置文件内容
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
更改之前通过new创建下层示例
//IAccountService as = new AccountServiceImpl();废弃
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
//private IAccountDao accountDao = new AccountDaoImpl();废弃
private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
工厂模式解耦的升级版
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个Key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
概念:把创建对象的权力交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入DI和依赖查找DL,之前用的BeanFactory类就使用了这个思想,可以消减计算机程序的耦合。它只能解决程序间的依赖关系,其他什么都做不了
配置文件bean.xml
客户端实现
public class Client {
public static void main(String[] args) {
/**
* 获取spring的Ioc核心容器,并根据id获取对象
*
* ApplicationContext的三个常用实现类:
* ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
* FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
* AnnotationConfigApplicationContext:它是用于读取注解创建容器的,是下次的内容
*
* 核心容器的两个接口引发出的问题:
* ApplicationContext: 单例对象使用
* 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象
* BeanFactory: 多例对象使用
* 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象
*/
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
IAccountService as = (IAccountService)ac.getBean("acountService");
IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
}
创建bean的三种方式
使用默认构造函数创建:在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创造。
使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
bean的作用范围调整:bean标签的scope属性:
bean的生命周期
单例对象
多例对象
xml中的标签定义
//init和destroy方法已经在实现类中定义好了
概念:在当前类需要用到其他类的对象,由spring为我们提供,以后依赖关系的管理都交给spring来维护,我们只需要在配置文件中说明。依赖关系的维护就称之为依赖注入
能注入的数据:
基本类型和String
其它bean类型(在配置文件中或者注解配置过的bean,就是使用ref的这个)
复杂类型/集合类型
用于给List结构集合注入的标签:list array set
用于给Map结构集合注入的标签:map props
结构相同,标签可以互换,如
AAA
BBB
CCC
注入的方式:
使用构造函数提供(把数据传入构造方法的参数)
使用方法
//String类型
//Interger类型
//Date类型
优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:改变了bean对象的实例化方式,是我们在创建对象时如果用不到这些数据也必须提供
使用set方法提供(get方法没必要) 更常用
使用方法
scope=" init-method="" destroy-method="">
用于创建对象的:作用和XML配置文件中编写一个标签实现的功能是一样的
在要被创建的对象的类上写上Component注解
//作用:用于把当前类对象存入spring容器中
//属性:value:用于指定bean的id。不写的时候默认是当前类名,且首字母改小写
@Component("accountService")
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
public AccountServiceImpl() {
System.out.println("对象创建了");
}
public void saveAccount() {
accountDao.saveAccount();
}
}
现在还不能扫描找到,需要在官网找到xml的配置代码
//这行自己配
现在在客户端类中可以正常使用了,控制台输出:“对象创建了”
public class Client {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
System.out.println(as);
}
}
其他用于创建对象的注解
Controller:一般用在表现层
Service:一般用在业务层
Repository:一般用在持久层
以上三个注解的作用和属性于Component一模一样,是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
用于注入数据的:作用就和xml配置文件中的bean标签中写一个标签的作用是一样的
Autowired:
Qualifier:
Resource
用于改变作用范围的:作用就和在bean标签中使用scope属性实现的功能是一样的
和生命周期相关(了解):作用就和在bean标签中使用init-method和destroy-method的作用是一样的
xml文件配置,基本用到了前面的内容
//如何创建bean对象
//如何注入数据,注入的两种类型之其它bean类型
//注入数据的两种方式之set方法注入
注入数据的两种方式之构造函数注入
//注入的两种类型之基本类型和String
在每个测试方法前面加上获取bean对象,其他操作同之前不变,如:
@Test
public void testFindAll(){
//之后下面这两行可以在spring整合junit中解决
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//根据id获取bean对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
List accounts = as.findAllAccount();
for (Account account : accounts) {
System.out.println(account);
}
}
用注解改造
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
@Autowired
private AccountDaoImpl accountDao;
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
并且在bean对象中添加扫描属性以及修改成context头
用配置类代替bean.xml文件(这节课有点偷懒了)
package config;
/**
* 该类是一个配置类,它的作用和bean.xml是一样的
* spring中的新注解
* Configuration
* 作用:指定当前类是一个配置类
* 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
* ComponentScan
* 作用:用于通过注解指定spring在创建容器时要扫描的包
* 属性:
* value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
* 我们使用此注解就等同于在xml中配置了:
*
* Bean
* 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
* 属性:
* name:用于指定bean的id。当不写时,默认值是当前方法的名称
* 细节:
* 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
* 查找的方式和Autowired注解的作用是一样的
* Import
* 作用:用于导入其他的配置类
* 属性:
* value:用于指定其他配置类的字节码。
* 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
* PropertySource
* 作用:用于指定properties文件的位置
* 属性:
* value:指定文件的名称和路径。
* 关键字:classpath,表示类路径下
*/
//@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
//这是共同的配置类
}
子配置类(针对一些特定的配置)
public class JdbcConfig {
//读取properties配置文件内容的一个好方法
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
//Qualifier可以用在参数位置上,选择对象具体的哪个实现类
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
//不用写死了
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
//一个对象有多个实现类的情况
@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
使用xml配置还是注解配置
spring整合junit
整合的思路
整合的步骤
/**
* 使用Junit单元测试:测试我们的配置
* Spring整合junit的配置
* 1、导入spring整合junit的jar(坐标)
* 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
* @Runwith
* 3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
* @ContextConfiguration
* locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
* classes:指定注解类所在地位置
*
* 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//3.执行方法
List accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("test anno");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//3.执行方法
as.deleteAccount(4);
}
}
问题:转账操作需要添加事务,在业务层实现每个功能都要放在事务的生命周期中,而且加上事务类还有复杂的bean.xml配置,耦合度非常高
使用动态代理可以解决,回顾基于接口的动态代理
/**
* 一个生产者
*/
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);
}
}
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
*/
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
}
基于子类的动态代理
/**
* 模拟一个消费者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行该对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
使用基于接口的动态代理给原Service方法增加事务
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
bean.xml中配置注入
概念:它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的技术上,对我们的已有方法进行增强。可以减少重复代码,提高开发效率,而且维护方便
关于代理的选择:spring中会根据目标类是否实现了接口来觉得采用哪种动态代理的方式
相关术语(概念性的,有利于后期自学)
Joinpoint(连接点):是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。例如我们在业务层的方法都是连接点
Pointcut(切入点):是指我们要对哪些Joinpoint进行拦截的定义。切入点就是被增强的方法,是连接点的子集。例如对账户的增删改查
Advice(通知/增强):通知是指拦截到Joinpoint之后所要做的事情
Introduction(引介):在不修改类代码的前提下,可以在运行期为类动态地添加一些方法或Field
Target(目标对象):被代理对象
Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入
Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
Aspect(切面):是切入点和通知(引介)的结合。仔细理解
学习Spring中的AOP要明确的事
开发阶段(我们要做的)
编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP 编程人员来做。
在配置文件中,声明切入点与通知间的关系,即切面。:AOP 编程人员来做。
运行阶段(Spring框架完成的)
Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
实战演示
编写Service方法,和一个用于记录日志的前置通知方法,对Service中的saveAccount方法进行增强
基于XML配置
四种常用通知类型+切入点表达式+环绕通知的xml配置
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 aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
先修改bean.xml文件内容
修改通知方法Logger
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.itheima.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方法开始记录日志了。。。");
}
/**
* 环绕通知
* 问题:
* 当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
* 分析:
* 通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
* 解决:
* Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
* 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
*
* spring中的环绕通知:
* 它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
*/
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
如果想彻底删除bean.xml文件
@Configuration
@ComponentScan(basePackages="com.itheima")
@EnableAspectJAutoProxy //添加这个
public class SpringConfiguration {
}
Factory是添加事务的工厂类,可以用aop代替
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
bean.xml中导入aop的约束并添加配置
改造成注解配置
简介
基本使用
导包 jdbc和tx(事务相关)
先用老方法使用
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("root");
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(ds);
jt.execute("insert into account(name,money)values('ccc',100)");
}
使用IOC解耦以及属性注入
bean.xml文件
main文件
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
jt.execute("insert into account(name,money)values('ddd',222)");
CRUD
代码部分
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
// jt.execute("insert into account(name,money)values('ddd',222)");
//增加一行数据
jt.update("insert into account(name,money)values(?,?)","eee",111f);
//删除一行数据
jt.update("delete from account where id = ?",3);
//修改一条数据
jt.update("update account set name=?,money=? where id=?","qwe",1234,4);
//查询所有数据
//使用BeanPropertyRowMapper就可以不用自己实现RowMapper接口了
List accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper(Account.class), 500f);
for (Account account : accounts) {
System.out.println(account);
}
//查询一个数据
List accounts = jt.query("select * from account where id = ?", new BeanPropertyRowMapper(Account.class), 1);
System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));
//查询一行一列
Integer count = jt.queryForObject("select count(*) from account where money > ?", Integer.class, 500f);
System.out.println(count);
}
分析方法
在Dao中的使用
创建接口和实现类,并且注入jdbcTemplate
//Dao层中下面两行可以抽取出来,继承spring中的JdbcDaoSupport(老师又默默地带我们手撕源码)。但是就不能在下面这行加@Autowired注解了
private JdbcTemplate jt;
public void setJt(JdbcTemplate jt) {
this.jt = jt;
}
目的:使用spring代替之前自己定义的事务方法
@Component("txManager")
@Aspect
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
/**
* 开启事务
*/
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();
}
}
@Around("pt1()")
public Object aroundAdvice(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();
this.beginTransaction();
rtValue = pjp.proceed(args);
this.commit();
return rtValue;
}catch (Throwable e){
this.rollback();
throw new RuntimeException(e);
}finally {
this.release();
}
}
}
基于xml的声明式事务控制
在官方文档中找到并引入tx约束
基于注解的声明式事务控制
加入xmlns:context的约束
配置spring创建容器时要扫描的包
添加其他service和dao注解同上
如下
基于纯注解的声明式事务控制
/**
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}
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;
}
}
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=root
@ContextConfiguration(classes= SpringConfiguration.class)
三层架构
优势
清晰的角色划分: 前端控制器(DispatcherServlet) 请求到处理器映射(HandlerMapping) 处理器适配器(HandlerAdapter) 视图解析器(ViewResolver) 处理器或页面控制器(Controller) 验证器( Validator) 命令对象(Command 请求参数绑定到的对象就叫命令对象) 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)。
分工明确,而且扩展点相当灵活,可以很容易扩展,虽然几乎不需要。
由于命令对象就是一个 POJO,无需继承框架特定 API,可以使用命令对象直接作为业务对象。
和 Spring 其他框架无缝集成,是其它 Web 框架所不具备的。
可适配,通过 HandlerAdapter 可以支持任意的类作为处理器。
可定制性,HandlerMapping、ViewResolver 等能够非常简单的定制。
功能强大的数据验证、格式化、绑定机制。
利用 Spring 提供的 Mock 对象能够非常简单的进行 Web 层单元测试。
本地化、主题的解析的支持,使我们更容易进行国际化和主题的切换。
强大的 JSP 标签库,使 JSP 编写更容易。
………………还有比如RESTful风格的支持、简单的文件上传、约定大于配置的契约式编程支持、基于注解的零配置支持等等。
和Struts2的优略分析
共同点:它们都是表现层框架,都是基于 MVC 模型编写的。
它们的底层都离不开原始 ServletAPI。
它们处理请求的机制都是一个核心控制器。
区别:Spring MVC 的入口是 Servlet, 而 Struts2 是 Filter
Spring MVC 是基于方法设计的,而 Struts2 是基于类,Struts2 每次执行都会创建一个动作类。所 以 Spring MVC 会稍微比 Struts2 快些。
Spring MVC 使用更加简洁,同时还支持 JSR303, 处理 ajax 的请求更方便 (JSR303 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注 解加在我们 JavaBean 的属性上面,就可以在需要校验的时候进行校验了。)
Struts2 的 OGNL 表达式使页面的开发效率相比 Spring MVC 更高些,但执行效率并没有比 JSTL 提 升,尤其是 struts2 的表单标签,远没有 html 执行效率高。
通过SpringMVC完成一个页面的跳转。分析
在index.jsp中写个跳转的超链接
配置web.xml
Archetype Created Web Application
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
dispatcherServlet
/
配置springmvc.xml
编写controller代码
@Controller //这个不是MVC的注解,是扫描的注解
public class HelloController {
@RequestMapping(path = "/hello")
public String sayHello(){
System.out.println("Hello StringMVC");
return "success";
}
}
在跳转到的success.jsp中写出访问成功
项目结构
大概的流程
当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以会创建DispatcherServlet对象, 就会加载springmvc.xml配置文件
开启了注解扫描,那么HelloController对象就会被创建
从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解 找到执行的具体方法
根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件
Tomcat服务器渲染页面,做出响应
详细的流程以及组件分析
前端控制器(DispatcherServlet)
处理器映射器(HandlerMapping)
处理器(Handler)
处理器适配器(HandlAdapter)
视图解析器(View Resolver)
视图(View)
RequestMapping注解的作用是建立请求URL和处理方法之间的对应关系
RequestMapping注解可以作用在方法和类上
RequestMapping的属性
入门程序
新建param.jsp
参数绑定入门程序
测试参数
Controller可以通过参数接收
@Controller
@RequestMapping("/param")
public class ParamController {
@RequestMapping("/testParam")
public String testParam(String username,String password){
System.out.println("用户名是:"+ username);
System.out.println("密码是:"+ password);
return "success";
}
}
绑定实体类型
页面改成表单提交
新建Account类和User类,User类是Accont类中的属性
controller
@RequestMapping("/saveAccount")
public String saveAccount(Account account){
System.out.println(account);
return "success";
}
解决中文乱码问题
以前通过配置request.setCharacterEnCoding方法解决
现在通过在web.xml中配置过滤器
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
characterEncodingFilter
/*
绑定集合类型
Account实体类中添加List和Map集合的属性
表单的值修改成如下格式
用户姓名:
用户年龄:
用户姓名:
用户年龄:
自定义类型转换器
问题:2020-11-11识别不了,MVC只能自动转换2020/11/11类型的
创建一个类实现Converter接口
public class StringToDateConverter implements Converter{
/**
* String source 传入进来字符串
* @param source
* @return
*/
public Date convert(String source) {
// 判断
if(source == null){
throw new RuntimeException("请您传入数据");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
// 把字符串转换日期
return df.parse(source);
} catch (Exception e) {
throw new RuntimeException("数据类型转换出现错误");
}
}
}
配置springmvc.xml
获取Servlet原生的API
直接在Controller的方法里面传参数就行了
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了...");
System.out.println(request);
HttpSession session = request.getSession();
System.out.println(session);
ServletContext servletContext = session.getServletContext();
System.out.println(servletContext);
System.out.println(response);
return "success";
}
RequestParam
问题:之前的请求中参数是什么名称,控制器中的形式参数也必须是这个名称,如果不是,就需要使用这个参数
属性
用在参数的前面
public String testRequestParam(@RequestParam(name="name") String username)
RequestBody
PathVariable
作用:用于绑定url中的占位符。例如:请求url中/delete/{id},这个{id}就是url占位符。url支持占位符是spring3.0之后加入的。是springmvc支持rest风格URL的一个重要标志
属性:
restful编程风格
在jsp和controller中的使用
RequestParam
@RequestMapping(path = "/testPathVariable/{sid}")
public String testPathVariable(@PathVariable("sid") String id){
System.out.println(id); //输出10
return "success";
}
HiddentHttpMethodFilter
RequestHeader
CookieValue
ModelAttibute
作用:它可以用于修饰方法和参数,出现在方法上,表示当前的方法会在控制器的方法执行之前先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法
属性:value:用于获取数据的key,key可以是pojo的属性名称,也可以是map结构的key
应用场景:当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据
例子:用户有uname,age,date三个字段,但是表单只提交uname和age,如果想让date保持数据库原来的值可以使用这个注解
有返回值
@ModelAttribute
public void showUser(String name){
System.out.println("showUser执行了");
User user = new User();
user.setUname(name);
user.setAge(20);
user.setDate(new Date());
map.put("abc",user);
}
@ModelAttribute
public User showUser(String name){
System.out.println("showUser执行了");
User user = new User();
user.setUname(name);
user.setAge(20);
user.setDate(new Date());
return user;
}
有返回值
@RequestMapping(path = "/testModelAttribute")
public String testModelAttribute(@ModelAttribute("abc") User user){
System.out.println(user);
return "success";
}
@ModelAttribute
public void showUser(String name, Map map){
System.out.println("showUser执行了");
User user = new User();
user.setUname(name);
user.setAge(20);
user.setDate(new Date());
map.put("abc",user);
}
SessionAttribute
作用:用于多次执行控制器方法间的参数共享
属性:
例子
创建三个超链接分别指向三个controller
controller
@Controller
@RequestMapping("/anno")
@SessionAttributes(value = {"msg"}) //只能作用到类上
public class AnnoController {
@RequestMapping(path = "/testSessionAttribute")
public String testSessionAttribute(Model model){
model.addAttribute("msg","美美");
return "success";
}
@RequestMapping(path = "/getSessionAttribute")
public String getSessionAttribute(ModelMap modelMap){
modelMap.get("msg");
return "success";
}
@RequestMapping(path = "/delSessionAttribute")
public String delSessionAttribute(SessionStatus status){
status.setComplete();
return "success";
}
}
success.jsp
${msg}
${sessionScope}
返回值是String类型
将查询出的对象存入request域中
@RequestMapping("/testString")
public String testString(Model model){
System.out.println("testString方法执行了。。。");
//模拟从数据库中查询出User对象
User user = new User();
user.setUsername("张三");
user.setPassword("123");
user.setAge(20);
model.addAttribute("user",user);
return "success";
}
再在前端通过el表达式显示
${user.username}
${user.password}
返回值是void类型
默认会发送到WEB-INF目录下对应访问地址的jsp文件
可以按照传统方法指定显示路径
@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().write("你好");
return;
}
返回值是ModelAndView类型
与返回值是String类型差不多,那个其实就是以这个作为底层原理
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){ //这个接口作为形式参数是怎么发挥作用的
System.out.println("testString方法执行了。。。");
ModelAndView mv = new ModelAndView();
//模拟从数据库中查询出User对象
User user = new User();
user.setUsername("李四");
user.setPassword("321");
user.setAge(20);
mv.addObject("user",user);
mv.setViewName("success");//可以使用视图解析器
return mv;
}
使用使用forward和redirect进行页面跳转
使用关键字的方法进行转发或重定向
@RequestMapping("/testForwardAndRedirect")
public String testForwardAndRedirect(){ //这个接口作为形式参数是怎么发挥作用的
System.out.println("testForwardAndRedirect。。。");
//请求转发
// return "forward:/WEB-INF/pages/success.jsp";
//重定向
return "redirect:/index.jsp"; //该jsp文件放在webapp目录下
}
过滤静态资源
引入jQuery,并绑定一个点击事件
Title
在springmvc.xml中设置静态资源不过滤
响应json数据之发送ajax的请求
用如下代码替换上面的alert
$.ajax({
url:"user/testAjax",
contentType:"application/json;charset=UTF-8",
data:'{"username":"hehe","password":"123","age":30}',
dataType:"json",
type:"post",
success:function (data) {
}
});
模拟异步请求响应
@RequestMapping("/testAjax")
public void testAjax(@RequestBody String body){
System.out.println("testAjax。。。");
System.out.println(body);
}
显示结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BiGLMf15-1605952530252)(C:\Users\chenzhijian\AppData\Roaming\Typora\typora-user-images\image-20201113162537554.png)]
响应json数据之响应json格式数据
导入jackson的jar包,使得json字符串和javaBean对象可以互相转换
将前端传过来的json数据包装到user对象中
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user){
System.out.println("testAjax。。。");
System.out.println(user);
user.setUsername("haha");
user.setAge(40);
return user;
}
前端显示后端更改的数据
success:function (data) {
alert(data);
alert(data.username);
alert(data.password);
alert(data.age);
}
原理分析
代码实现
引入fileupload和io的pom依赖
配置解析器对象
创建表单
controller代码
@RequestMapping("/fileupload")
public String fileUpload(HttpServletRequest request, MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样
System.out.println("文件上传...");
//上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
//判断该路径是否存在
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
//设置唯一文件名
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid+"_"+filename;
//上传文件
upload.transferTo(new File(path,filename));
return "success";
}
分析
代码实现
引入jersey的pom依赖
创建表单
代码实现
@RequestMapping("/fileupload2")
public String fileUpload2(MultipartFile upload) throws Exception { //upload这个必须与上传文件表单的name属性值一样
System.out.println("服务器文件上传...");
//上传的位置
String path = "http://localhost:9090/uploads/";
//设置唯一文件名
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid+"_"+filename;
//创建客户端对象
Client client = Client.create();
//建立连接
WebResource webResource = client.resource(path+filename);
//上传文件
webResource.put(upload.getBytes());
return "success";
}
出现403forbidden
更改tomcat配置,详细见https://blog.csdn.net/Dawn510/article/details/103915414
Controller调用service,service调用dao,异常都是向上抛出的,最终有DispatcherServlet找异常处理器进行异常的处理
controller
@RequestMapping("/testException")
public String fileException() throws SysException {
System.out.println("Exception...");
try {
int i = 10/0;
} catch (Exception e) {
e.printStackTrace();
throw new SysException("查询所有用户出现了错误。。。");
}
return "success";
}
编写自定义异常类
public class SysException extends Exception{
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public SysException(String message) {
this.message = message;
}
}
编写异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
SysException e = null;
if(ex instanceof SysException){
e = (SysException) ex;
}else {
e = new SysException("系统正在维护");
}
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e.getMessage());
mv.setViewName("error");
return mv;
}
}
编写error页面
配置异常处理器
拦截器的作用
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
与过滤器的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了/*之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦 截的。
它也是 AOP 思想的具体应用。 我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。
拦截器类
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("拦截器方法执行了。。。");
return true;
}
}
springmvc.bean中配置拦截器
jsp中输出
访问成功
<% System.out.println("success.jsp执行了..."); %>
结果 拦截器->controller->jsp
三种处理方法的总结:
如果配多个拦截器
在resources资源文件中创建spring的配置文件以及引入log4j
在业务层添加@Service(“accountService”)注解
在测试类中测试成功
@Test
public void run1(){
ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
AccountService as = (AccountService) ac.getBean("accountService");
as.findAll();
}
先搭建SpringMVC的环境,测试能不能独立运行
配置web.xml
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:springmvc.xml
1
dispatcherServlet
/
characterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
characterEncodingFilter
/*
配置springmvc.xml
index.jsp页面
测试
Controller
@Controller("accountController")
@RequestMapping("/account")
public class AccountController {
@RequestMapping("/findAll")
public String testFindAll(){
System.out.println("表现层:找到所有列表");
return "list"; //list.jsp输出文字提示信息
}
}
整合SpringMVC
先搭建Mybatis环境
创建主配置文件sqlMapConfig.xml
用注解写两个方法的sql语句
测试查询
@Test
public void run1() throws Exception {
// 加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 创建工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建sqlSession对象
SqlSession session = factory.openSession();
// 获取代理对象
AccountDao dao = session.getMapper(AccountDao.class);
// 调用查询的方法
List list = dao.findAll();
for (Account account : list) {
System.out.println(account);
}
// 释放资源
session.close();
inputStream.close();
}
测试保存(增删改需要自己提交事务)
@Test
public void run2() throws Exception {
Account account = new Account();
account.setName("熊大");
account.setMoney(300d);
// 加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 创建工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建sqlSession对象
SqlSession session = factory.openSession();
// 获取代理对象
AccountDao dao = session.getMapper(AccountDao.class);
// 调用查询的方法
dao.saveAccount(account);
//提交事务
session.commit();
// 释放资源
session.close();
inputStream.close();
}
整合MyBatis框架
将MyBatis配置类中的内容移到spring的配置类applicationContext.xml中,进入ioc容器,完成后可以删除sqlMapConfig.xml
在Service中注入Dao,并且调用其方法,返回
@Override
public List findAll() {
System.out.println("业务层:查询所有的账户信息");
return accountDao.findAll();
}
Controller存入域对象中,让页面能够读取
@RequestMapping("/findAll")
public String findAll(Model model){
System.out.println("表现层:查询所有的账户信息");
List list = accountService.findAll();
model.addAttribute("list",list);
return "list";
}
jsp页面显示出数据
${account.name}
配置事务,写保存账户方法
配置Spring框架声明式事务管理
//尽可能避开整体扫描
写一个提交表单
Controller调Service,Service调Dao
@RequestMapping("/save")
public void save(Account account, HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("表现层:保存账户信息");
accountService.saveAccount(account);
response.sendRedirect(request.getContextPath()+"/account/findAll");
return;
}
完成