SSM框架学习(踩坑)
Mybatis:
day1:
三层架构
表现层:用于展示数据
业务层:处理业务需求
持久层:和数据库进行交互
持久层技术解决方案
JDBC技术:Connection
PreparedStatement
ResultSet
Spring的JdbcTemplate:Spring中对jdbc的简单封装
Apache的DBUtils:它和Spring的JdbcTemplate很像
JDBC是规范
Spring的JdbcTemplate和Apache的DBUtils都只是工具类
Mybatis概述:mybatis是一个持久层框架,用java编写
它封装了jdbc操作的很多细节,使开发者只需关注sql语句本身,无需关注注册驱动,创建链接等繁杂过程
它使用了ORM的思想实现了结果集的封装
ORM:Object Relational Mapping 对象关系映射
简单来说:就是把数据库表和实体类及实体类属性对应起来
让我们可以操作实体类就可以操作数据库表
实体类中的属性和数据库表中的字段名称保持一致
Mybatis入门:
mybatis的环境搭建
第一步:创建maven工程导入坐标(pom.xml)
第二步:创建实体类和dao接口
第三步:创建mybatis的主配置文件:SqlMapConfig.xml
第四步:创建映射配置文件:IUserDao.xml
环境搭建注意事项:
第一个:创建IUserDao.xml和IUserDao.java时名称是为了和我们之前的知识保持一致,在mybatis中它把持久层的接口名称和映射文件也叫做:Mapper。Mapper和DAO含义一致。即IUserDao=IUserMapper
第二个:在idea中创建目录的时候,它和包是不一样的
包在创建的时候:com.itheima.dao是三级结构
目录在创建时,com.ithema.dao是一级目录
第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
第五个:映射配置文件的操作配置,id属性的取值必须是dao接口的方法名
当我们遵从了第三,四,五点之后,我们开发中就无需再写dao的实现类
mybatis入门案例:
第一步:读取配置文件
第二步:创建SQLSessionFactory工厂
第三步:创建SQLSession
第四步:创建Dao接口的代理对象
第五步:执行dao中的方法
第六步:释放资源
注意事项:不要忘记映射配置中告知mybatis要封装到哪个实体类中
配置的方式:指定实体类的全限定类名
mybatis基于注解的入门案例:
把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定sql语句
同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定名。
明确:我们在实际开发时越简便越好,所以都是采用不写dao实现类的方式。
不管使用xml还是注解配置。但是mybatis支持写dao实现类的。
加载xml文件方法:第一个:使用类加载器,它只能读取类路径的配置文件
第二个:使用ServletContext对象的getRealPath()
InputStream in= Resources.getResourceAsStream(“SqlMapConfig.xml”);//有连接数据库的信息
//2.创建SQLSessionFactory工厂
//创建工厂mybatis使用了构建者模式,把对象的创建细节隐藏,使使用者直接调用方法即可拿到对象
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();//有工厂生产操作对象
SqlSessionFactory factory=rrbuilder.build(in);
//3.使用工厂生产SQLSession对象(对象用于Dao的实现)
//生产SQLSession使用了工厂模式,优势:解耦(降低类之间的依赖关系)
SqlSession session=factory.openSession();//操作数据库
//4.使用SQLSession创建Dao接口的代理对象
//创建Dao接口实现类使用了代理模式,优势:不修改源码的基础上对已有方法增强
IUserDao userDao=session.getMapper(IUserDao.class);//不是session直接操作,是通过Dao进行操作,我们只有Dao接口没有实现类,通过此句创建代理对象
//5.使用代理对象执行方法
List users=userDao.findAll();
for(User user :users){
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
SqlMapConfig,xml
//连接数据库的信息,有了他们就能创建Connection对象
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
//有了它就有了映射配置信息
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
IUserDao.xml:
//有了它就有了执行的SQl语句,就可以获取PreparedStatement
//还有封装的实体类全限定类名
<select id="findAll" resultType="com.itheima.domain.User">
select * from user
</select>
读取配置文件,用到的技术就是解析xml技术。:此处用的是dom4j解析xml技术
自定义Mybatis流程:
- 读取配置文件用到了io里面的Resource类,读出来的流(也就是找到的信息)交给构建者(SqlSessionFactoryBuilder),构建者使用了
- utils.XMlConfigBuilder工具类构建了了一个工厂对象,工厂对象的
- openSession(DefaultSqlSessionFactory)给我们提供了一个Session方法(DefaultSqlSession),在Session方法里面实现创建代理对象和查询所有操作
*/
Mybatis自定义流程再分析:
第一步:SqlSessionFactoryBuilder接收SqlMapConfig.xml文件流,构建出SqlSessionFactory对象
第二步:SqLSessionFactory读取SqlMapConfig.xml中连接数据库和mapper映射信息。用来生产出真正操作数据库的SqlSession对象
SqlSession对象的两大作用:1.生产接口代理对象。2.定义通用增删改查方法
OGNL表达式:Object Graphic Navigation Language
对象 图 导航 语音
它是通过对象的取值方法来获取数据。在写法上把get给省略了
比如:我们获取用户的名称
类中的写法:user.getUsername()
OGNL表达式写法:user.username
mybatis为什么能直接写username,而不用user.呢:
因为在parameterType中已经提供了属性所属的类,所有此时不需要写对象名,而是直接写属性名
MyBatis的参数深入:
parameterType:1.传递简单类型
2.传递pojo对象
3.传递pojo包装对象
Mybatis的输出结果封装:
1.输出简单类型
2.输出pojo对象(查询一个)
3.输出pojo列表(查询所有)
开发中通过 pojo 传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查 询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。 Pojo 类中包含 pojo。 需求:根据用户名查询用户信息,查询条件放到 QueryVo 的 user 属性中。
3.2.1 编写 QueryVo
/** * *
Title: QueryVo
-
Description: 查询条件对象
*Company: http://www.itheima.com/
*/
public class QueryVo implements Serializable {
private User user;
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
}
public void testFindByQueryVo()
{
QueryVo vo = new QueryVo();
User user = new User();
user.setUserName("%王%");
vo.setUser(user);
List users = userDao.findByVo(vo);
for(User u : users)
{ System.out.println(u);
}
}
IUserDao.xml
<!--<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>-->
6.3 typeAliases(类型别名)
在前面我们讲的 Mybatis 支持的默认别名,我们也可以采用自定义别名方式来开发。
6.3.1 自定义别名:
在 SqlMapConfig.xml 中配置:
6.4.2
使用 mapper 接口类路径 如: 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
6.4.3
注册指定包下的所有 mapper 接口 如: 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
1.连接池:我们再实际开发中都会使用连接池。因为它可以减少我们获取连接所消耗的时间
连接池就是用于存储连接的一个容器,容器其实就是一个集合对象,该集合必须是线程安全的,不能两个线程拿到统一连接。该集合还必须实现队列的特性,先进先出。
2,Mybatis中的连接池提供了3种方式的配置:
配置的位置:
主配置文件SqlMapConfig.xml的dataSource标签,type属性表示采用何种连接池方式。
type取值:POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis有针对规范的实现。
UNPOOLED:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
JDI:采用服务器提供的JNDI技术实现来获取DataSource对象,不同的服务器能拿到的DataSource是不一样的,如果不是web或者Maven的war工程是不能使用的。
我们课程中使用的是Tomcat服务器,采用连接池就是dbcp连接池。
什么是事务:
事务的四大特性:ACID
不考虑隔离性会产生的3个问题
解决方法:四种隔离级别
3.mybatis中的事务:它是通过sqlsession对象的commit方法和roolback方法实现事务的提交和回滚
Mybatis 的动态 SQL 语句
动态 SQL 之标签
动态 SQL 之标签
动态标签之标签
<!--<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user where 1=1
<if test="username !=null">
and username=#{username}此处的=#{username}是实体类中的username
</if>
<if test="usersex!=null">
and sex=#{sex}
</if>
</select>-->
<!--为了简化上面 where 1=1 的条件拼装,我们可以采用<where>标签来简化开发。-->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user
<where>
<if test="username !=null">
and username=#{username}<!--此处的=#{username}是实体类中的username-->
</if>
<if test="sex!=null">
and sex=#{sex}
</if>
</where>
</select>
<!--根据QueryVo中的Id集合实现查询用户列表-->
<!--此处parameterType写的是queryvo而非com.itheima.domain.QueryVo是因为在SqlMapConfig,xml中配置了typeAliases,此时该类类名即为可代替的别名-->
<select id="findUserInIds" resultMap="userMap" parameterType="queryvo">
select * from user
<where>
<if test="ids !=null and ids.size()>0">
<foreach collection="ids" open="and id in(" close= ")" item="uid" separator=",">
#{uid} <!--此处是和item的取值一致-->
</foreach>
</if>
</where>
</select>
MyBatis中#{}和KaTeX parse error: Expected 'EOF', got '#' at position 14: {}的区别详解 区别 1.#̲将传入的数据都当成一个字符串,…,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
3.#方式能够很大程度防止sql注入。
4.
方
式
无
法
防
止
S
q
l
注
入
。
5.
方式无法防止Sql注入。 5.
方式无法防止Sql注入。5.方式一般用于传入数据库对象,例如传入表名.
6.一般能用#的就别用
.
M
y
B
a
t
i
s
排
序
时
使
用
o
r
d
e
r
b
y
动
态
参
数
时
需
要
注
意
,
用
. MyBatis排序时使用order by 动态参数时需要注意,用
.MyBatis排序时使用orderby动态参数时需要注意,用而不是#
#{}表示一个占位符号 通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类 型值,#{}括号中可以是 value 或其它名称。
表
示
拼
接
s
q
l
串
通
过
{}表示拼接 sql 串 通过
表示拼接sql串通过{}可以将 parameterType 传入的内容拼接在 sql中且不进行 jdbc 类型转换,
可
以
接
收
简
单
类
型
值
或
p
o
j
o
属
性
值
,
如
果
p
a
r
a
m
e
t
e
r
T
y
p
e
传
输
单
个
简
单
类
型
值
,
{}可以接收简 单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,
可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,{}括号中只能是 value。
实例讲解:
动态 sql 是 mybatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 mybatis 会对其进行动态解析。mybatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。
在下面的语句中,如果 name 的值为 zhangsan,则两种方式无任何区别:
select * from user where name = #{name};
select * from user where name = ${name};
其解析之后的结果均为
select * from user where name = ‘zhangsan’;
但是 #{} 和 ${} 在预编译中的处理是不一样的。
#{} 在预处理时,会把参数部分用一个占位符 ? 代替,变成如下的 sql 语句:
select * from user where name = ?;
而 ${} 则只是简单的字符串替换,在动态解析阶段,该 sql 语句会被解析成
select * from user where name = ‘zhangsan’;
以上,#{} 的参数替换是发生在 DBMS 中,而 ${} 则发生在动态解析过程中。
那么,在使用过程中我们应该使用哪种方式呢?
答案是:优先使用 #{}。因为 ${} 会导致 sql 注入的问题。
2.7 Mybatis 与 JDBC 编程的比较
1.数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。 解决: 在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。
2.Sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。 解决: 将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。 3.向sql语句传参数麻烦,因为sql语句的where 条件不一定,可能多也可能少,占位符需要和参数对应。 解决: Mybatis自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的 类型。 4.对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。 解决: Mybatis自动将 sql执行结果映射至 java 对象,通过statement中的resultType 定义输出结果的 类型。
Mybatis中的多表查询:
表之间的关系有几种:
一对多
多对一
一对一
多对多
举例:
一对多: 一个用户可以下多个订单
多个订单属于同一个用户
一对一:一个人只能有一个身份证号
一个身份证号只属于一个人
多对多:一个学生可以被多个老师教
一个老师可以交多个学生
特例:如果拿出一个订单,他都只能属于一个用户
所以Mybatis就把多对一看成了一对一
Mybatis中的多表查询:
步骤:1.建立两张表:用户表,账户表
让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
2.建立两个实体类:用户实体类和账户实体类
让用户和账户的实体类能体现出来一对多的关系
3.建立两个配置文件
用户的配置文件
账户的配置文件
4.实现配置
当我们查询用户时,可以同时得到用户下包含的账户信息
当我们查询账户时,可以同时得到账户的所属用户信息
//从表实体应该包含一个主表实体的对象引用(数据库表的一对一关系映射)
//一对多关系映射:主表实体应该包含从表实体的集合引用
Mybatis中的多表查询(多对多):
示例:用户和角色
一个用户可以有多个角色
一个角色可以赋予多个用户
步骤:1.建立两张表:用户表,角色表
让用户表和账户表之间具备多对多的关系:需要使用中间表,中间表中包含各自的主键,在中间表中是外键。
2.建立两个实体类:用户实体类和角色实体类
让用户和角色的实体类能体现出来多对多的关系
各自包含对方一个集合引用
3.建立两个配置文件
用户的配置文件
角色的配置文件
4.实现配置
当我们查询用户时,可以同时得到用户下包含的角色信息
当我们查询角色时,可以同时得到角色的赋予用户信息
第四天:
Mybatis中的延迟加载
什么是延迟加载:在真正使用数据时才发起查询,不用的时候不查询,按需加载(懒加载)
什么是立即加载:不管用不用,只要一调用方法,马上发起查询
在对应的四种表关系中:一对多,多对一,一对一,多对多
一对多,多对多(关联对象是“多”时):通常情况我们都是采用延迟加载
多对一,一对一(即关联对象是“一”时):通常情况采用立即加载
我们需要在 Mybatis 的配置文件 SqlMapConfig.xml 文件中添加延迟加载的配置。
IAccountDao.xml:
<!--一对一的关系映射:配置封装user的内容, 用association标签
select属性指定的内容:查询用户的唯一标识(IUserDao.xml下通过id查询用户的方法)
column属性指定的内容:用户根据id查询时,所需要的参数的值,给上面select的方法传参,此uid是account表中的uid-->
<association property="user" column="uid" javaType="user" select="com.itheima.dao.IUserDao.findById"></association>
</resultMap>
<!--查询所有-->
<select id="findALL" resultMap="accountUserMap"> <!--resultType是返回类型,此处使用的user是typeAlias配置的别名,位置是SqlMapConfig.xml,本名是com.itheima.domain.User-->
select * from account
</select>
IUserDao.xml:一对多的延迟加载
<!--延迟加载的思想是在用的时候调用对方配置文件中的一个配置来实现查询的功能-->
<!--配置user对象中accounts集合的映射,ofType指的是集合中元素的类型,
此处account是别用了名,不然该写全限定类名
select的方法就是对方配置文件中的方法来实现查询功能
column传的参数是查询语句中user表提供的参数-->
<collection property="accounts" ofType="account" select="com.itheima.dao.IAccountDao.findAccountByUid" column="id">
</collection>
</resultMap>
<select id="findAllAccount" resultMap="userAccountMap">
select * from user
</select>
Mybatis中的缓存:
什么是缓存:存在于内存中的临时数据
为什么使用缓存:减少和数据库的交互次数,提高执行效率
什么样的数据能使用缓存,什么样的数据不能使用:
适用于缓存的:经常查询并且不经常改变。
数据的正确与否对正确结果影响不大
不适用缓存的:经常改变的数据
数据的正确与否对正确结果影响很大
例如:商品的库存,银行的汇率,股市的牌价
mybatis中的一级缓存和二级缓存:
一级缓存:mybatis中SqlSession对象的缓存
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map.当我们再次查询同样的数据,mybatis会先去SQLSession
查看是否有,有的话就直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了。
一级缓存是 SqlSession 范围的缓存,当调用 SqlSession 的修改,添加,删除,commit(),close()等方法时,就会清空一级缓存。
二级缓存:它指的是Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
Mybatis中的注解开发
环境搭建
单表CRUD操作(代理DAO方式)
多表查询操作
缓存的配置
Spring框架:
day1:
1.1.1 spring 是什么
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:
反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring
MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多
著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
程序的耦合:
耦合:程序之间的依赖关系(独立性差)
包括:类之间的依赖
方法之间的依赖
解耦:降低程序之间的依赖关系
实际开发中:编译器不依赖,运行时才依赖
解耦的思路:
第一步:使用反射来创建对象,而避免使用new关键字
第二步:通过读取配置文件来获取要创建的对象全限定类名
2.1.2 解决程序耦合的思路
当是我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:
Class.forName(“com.mysql.jdbc.Driver”);//此处只是一个字符串
此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运
行就不要想了,没有驱动不可能运行成功的)。
同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。
解决这个问题也很简单,使用配置文件配置。
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的
方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
上一小节解耦的思路有 2 个问题:
1、存哪去?
分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。
到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
所以我们的答案就是
在应用加载时,创建一个 Map,用于存放三层对象。
我们把这个 map 称之为容器。
2、还是没解释什么是工厂?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
原来:
我们在获取对象时,都是采用 new 的方式。是主动的。
现在:
我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。
IOC:控制反转:把创建对象的权利交给工厂,而不是根据自己的想去去new
明确 ioc 的作用:
削减计算机程序的耦合(解除我们代码中的依赖关系)。
<!--spring对bean的管理细节
1.创建bean的三种方式
2.bean对象的作用范围
3.bean对象的生命周期-->
<!--当遇到引用的jar包中的类时,用第二种或者第三种方式-->
<!--创建Bean的方式-->
<!--第一种方式:使用默认构造函数创建。
在Spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建-->
<!--<bean id="accountService" class="com.itheima.service.imp.AccountServiceImpl"></bean>-->
<!--第二种方法:使用普通工厂中的方法创建对象(使用某个类(可以是别人提供的类)中的方法创建对象,并存入Spring容器)-->
<!--<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>-->
<!--第三种方式:使用工厂中的静态方法创建对象(使用类中的静态方法创建对象,并存入Spring容器)-->
<!--<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>-->
<!--bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值:
singleton:单例(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就同session-->
<!--bean对象的生命周期
单例对象:
出生:当容器创建时对象出生(一解析完配置文件)
活着:只有容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象:
出生:当我们使用对象时Spring框架为我们创建
活着:对象只要是使用过程中就一直活着
死亡:当对象长时间且没有别的对象引用时,用java的垃圾回收器回收。
-->
<!--构造函数注入
使用的标签:constructor -arg
标签出现的位置:bean标签内部
标签的属性
index:指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置从0 开始
type:指定指定要注入的数据的数据类型,该数据类型也是构造函数中的某个或某些数据类型
name:指定给构造函数中指定名称的参数赋值
=======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型数据,它指的是在spring的IOC核心容器中出现过的bean对象
优势:在获取bean对象时,注入的数据是必须的操作,否则对象无法创建成功
弊端:改变了bean对象的实例化方式,使我们在创建对象时,如果不使用这些数据,也必须提供
-->
<bean id="accountService" class="com.itheima.service.imp.AccountServiceImpl">
<constructor-arg name="name" value="华艺轩"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!--读取class=""中的全限定类名,反射创建一个对象,并且存入spring和核心容器中,通过id=""把对象取出-->
<bean id="now" class="java.util.Date"></bean>
<!--set方法注入 常用的方式
涉及的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定注入时所调用的 “set方法”名称
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型数据,它指的是在spring的IOC核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数,去掉其中任意一个property人可以运行
弊端:如果有某个成员必须有值,则获取对象时set方法没有执行,
-->
<bean id="accountService2" class="com.itheima.service.imp.AccountServiceImpl2">
<property name="Name" value="Test"></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<!--复杂类型标签的注入/集合类型的注入
用于给List结构注入的标签:list,array,set
用于给Map结构注入的标签:map,props
结构相同,标签可以互换-->
<bean id="accountService3" class="com.itheima.service.imp.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<!-- 注入 Map 数据 -->
<property name="myMap">
<props>
<prop key="testA">aaa</prop>
<prop key="testB">bbb</prop>
</props>
</property>
<!-- 注入 properties 数据 -->
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>bbb</value>
</entry>
</map>
</property>
<!-- 注入 list 集合数据 -->
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
</bean>
Day7:动态代理:
/**
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上方法增强
* 分类:基于接口的动态代理
* 基于子类的动态代理
*
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象 :
* 要求: 被代理类不能是最终类(如果是最终类就不能创建子类)
* 创建的方式 :Enhancer.create(参数)
* 参数:字节码:它是用于指定被代理对象的字节码
* Callback:用于提供增强的代码:
* 它是让我们写如何代理。我们一般都是写一些该接口的实现类,通常情况下都是匿名内部类,但不是必须的
* 此接口实现类都是谁用谁写
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行被代理对象的任何接口方法都会经过该方法,此方法有拦截作用
*
* @param proxy
* @param method
* @param args 以上三个参数和基于接口的动态代理中invoke方法参数一样
* @param methodProxy:当前执行方法的代理对象
* @return
* @throws Throwable
*/
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);
}
-
}基于接口的动态代理: * 涉及的类:proxy * 提供者:JDK官方 * 如何创建代理对象 : * 要求: 被代理类最少实现一个接口 ,如果没有不能使用 * 创建的方式 :Proxy.newProxyInstance(三个参数) * 参数:ClassLoader:类加载器: * 它用于加载代理对象字节码的,和被代理对象使用相同的类加载器。固定写法 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同的方法。固定写法 * InvocationHandler:用于提供增强的代码 * 它是让我们写如何代理,我们一般都是写一些接口的实现类,通常情况下都是匿名内部类,但不是必须的 * 此接口的实现类都是谁用谁写 */ IProducer proxyProducer=(IProducer)Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** * invoke方法的作用: * 方法参数含义执行被代理对象的任何接口方法都会经过该方法,此方法有拦截作用 * @param proxy :代理对象引用,不一定每次用的到 * @param method :当前执行方法 * @param args :当前执行方法所需的参数 * @return 和被代理对象方法有相同的返回值 * @throws Throwable */ 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(1000f);
AOP:面向切面编程:
作用:在程序运行期间,不修改源码对已有的方法进行增强
优势:减少重复代码
提高开发资源
维护方便
Joinpoint(连接点): 所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的 连接点。
Pointcut(切入点): 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。(进行增强的方法)
XML文件中AOP的配置:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--spring基于xml的AOP配置
1.把通知Bean也交给spring来管理
2.使用aop:config标签表明开始AOP配置
3.使用aop:aspect标签表明开始配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的id
4.在aop:aspect标签内部使用对应标签来配置通知的类型
我们现在示例让printLog方法在切入点方法执行之前执行,所有事前置通知
aop:before :表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
point-cut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名.包名...类名.方法名(参数列表)
标准写法:public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值 * com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包就需要写几个*:* *.*.*.*.AccountServiceImpl.saveAccount()
包名可以使用..表示当前包及其子包* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配:* *..*.*()
参数列表:可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以用..表示有无参数都可,有参数可以是任意类型
全通配写法:* *..*.*(..)
实际开发中切入点表达式的通常写法
切到业务层实现类下的所以方法
* com.itheima.service.impl.*.*(..)
-->
<!--配置通知类Logger-->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容
此标签写在切面标签内部,只能当前标签使用。它还可以写在aop:aspect外面使用,此时变成所有切面可用,必须在配置切面之前-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="beforeprintLog" pointcut-ref="pt1"></aop:before>
<!--配置后置通知类型,在切入点方法正常执行时执行,它和异常通知只能执行一个-->
<aop:after-returning method="afterReturningprintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>
<!--配置异常通知类型,并且建立通知方法和切入点方法的关联-->
<aop:after-throwing method="afterThrowingprintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>
<!--配置最终通知类型,并且建立通知方法和切入点方法的关联-->
<aop:after method="afterprintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>
<!--<!–配置切入点表达式,id属性用于指定表达式的唯一标识,expression属性用于指定表达式内容
此标签写在切面标签内部,只能当前标签使用。它还可以写在aop:aspect外面使用,此时变成所有切面可用,必须在配置切面之前–>
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>-->
<!--配置环绕通知,详细的注释请看Logger类中-->
<aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
环绕通知:
/**
* 环绕通知
* 问题:当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了
* 分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有
* 解决:
* Spring框架为我们提供了一个接口,ProceedingJoinPoint.该接口有一个方法proceed()。此方法就相当于明确调用切入点方法
* 该接口可以作为环绕通知的方法参数,在程序执行时,Spring框架会为我们提供该接口的实现类供我们使用
*
* Spring中的环绕通知:
* 它是Spring框架为我们提供了一种可以在代码中手动控制增强方法何时执行的方法。
*/
@Around((“pt1()”))
public Object aroundPrintLog(ProceedingJoinPoint pip){
Object rtValue=null;
try {
Object[] args=pip.getArgs();
System.out.println(“环绕通知Logger方法中的printLog开始执行日志了,,,前置”);
rtValue=pip.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("环绕通知Logger方法中的printLog开始执行日志了,,,后置");
return rtValue;
} catch (Throwable t) {
System.out.println("环绕通知Logger方法中的printLog开始执行日志了,,,异常");
throw new RuntimeException(t);
}finally {
System.out.println("环绕通知Logger方法中的printLog开始执行日志了,,,最终");
}
}
注解配置AOP:
Spring第八天
spring中jdbcTemplate
JdbcTemplate的作用:用于和数据库交互,实现对表的CRUD操作
如何创建该对象:
对象中的常用方法:
/**
-
jdbcTemplate crud操作
*/
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
// //准备数据源,使用的是spring内置数据源
// DriverManagerDataSource ds=new DriverManagerDataSource();
// ds.setDriverClass(“com.mysql.jdbc.Driver”);
// ds.setJdbcUrl(“jdbc:mysql://localhost:3306/eesy”);
// ds.setUser(“root”);
// ds.setPassword(“123456”);
// //1.创建jdbcTemplate对象
// JdbcTemplate jt=new JdbcTemplate();
// jt.setDataSource(ds);
// jt.execute(“insert into account(name,money)values(‘ccc’,1000)”);//获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //获取对象 JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class); //执行操作 增删改都是update,查用query //保存 //jt.update("insert into account(name,money)values(?,?)","eee",333f); //更新 //jt.update("update account set name=?,money=? where id =?","test",4577,7); //删除 // jt.update("delete from account where id=?", 8); //查询所有 // List<Account> accounts=jt.query("select * from account where money>?", new AccountRowMapper(), 1000f); //实际开发中使用的是spring提供的这个实现,避免重新写AccountRowMapper类 List<Account> accounts=jt.query("select * from account where money>?", new BeanPropertyRowMapper<Account>(Account.class), 1000f); for(Account account:accounts){ System.out.println(account); } //查询一个 List<Account> accounts1=jt.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), 1); System.out.println(accounts1.isEmpty()?"没有内容":accounts1.get(0)); //查询返回一行一列(使用聚合函数,但不加group by子句),此处查询的返回类型和queryrunner类似,返回和语句中的Long.class类型一致,或者可以是Integer.class等 Long count=jt.queryForObject("select count(*) from account where money>?",Long.class,1000f); System.out.println(count);
}
}class AccountRowMapper implements RowMapper{
public Account mapRow(ResultSet resultSet, int i) throws SQLException {
Account account=new Account();
account.setId(resultSet.getInt(“id”));
account.setName(resultSet.getString(“name”));
account.setMoney(resultSet.getFloat(“money”));
return account;
}
}
//package com.itheima.dao.impl;
//
//import org.springframework.jdbc.core.JdbcTemplate;
//
//import javax.sql.DataSource;
//
///**
// *此类用于抽取DAO中的重复代码,此处代码spring已经给我们提供,为import org.springframework.jdbc.core.support.JdbcDaoSupport,AccountDaoImpl2直接继承;
// */
//public class JdbcDaoSupport {
// private JdbcTemplate jdbcTemplate;
//
// public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
// this.jdbcTemplate = jdbcTemplate;
// }
//
// public JdbcTemplate getJdbcTemplate() {
// return jdbcTemplate;
// }
//
// private DataSource dataSource;
//
// //此处代码表示,当没有jdbcTemplate对象只要dataSource时,可通过以下创建出带有dataSource的jdbcTemplate,
// // 此处修改后bean.xml中可修改AccountDaoImpl2的bean注入的代码,将注入参数改成dataSource,也可创建jdbcTemplate。
// public void setDataSource(DataSource dataSource) {
// this.dataSource = dataSource;
// if(jdbcTemplate==null){
// jdbcTemplate=createJdbcTemplate(dataSource);
// }
// }
// private JdbcTemplate createJdbcTemplate(DataSource dataSource){
// return new JdbcTemplate(dataSource);
// }
//}
Spring:
我们的开发架构一般都是基于两种形式,一种是 C/S 架构,也就是客户端/服务器,另一种是 B/S 架构,也就 是浏览器服务器。在 JavaEE 开发中,几乎全都是基于 B/S架构的开发。那么在 B/S架构中,系统标准的三层架构 包括:表现层、业务层、持久层。三层架构在我们的实际开发中使用的非常多,所以我们课程中的案例也都是基于
三层架构设计的。 三层架构中,每一层各司其职,接下来我们就说说每层都负责哪些方面:
表现层: 也就是我们常说的web层。它负责接收客户端请求,向客户端响应结果,通常客户端使用http协议请求 web 层,web 需要接收 http 请求,完成 http 响应。
表现层包括展示层和控制层:控制层(Controller)负责接收请求,展示层负责结果的展示。 表现层依赖业务层,接收到客户端请求一般会调用业务层进行业务处理,并将处理结果响应给客户端。 表现层的设计一般都使用 MVC 模型。(MVC 是表现层的设计模型,和其他层没有关系)
业务层: 也就是我们常说的 service 层。它负责业务逻辑处理,和我们开发项目的需求息息相关。web 层依赖业 务层,但是业务层不依赖
web 层。 业务层在业务处理时可能会依赖持久层,如果要对数据持久化需要保证事务一致性。(也就是我们说的, 事务应该放到业务层来控制) 持久层: 也就是我们是常说的 dao 层。负责数据持久化,包括数据层即数据库和数据访问层,数据库是对数据进 行持久化的载体,数据访问层是业务层和持久层交互的接口,业务层需要通过数据访问层将数据持久化到数据库
中。通俗的讲,持久层就是和数据库交互,对数据库表进行曾删改查的.
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写, 是一种用于设计创建 Web 应用程序表现层的模式。MVC 中每个部分各司其职:
Model(模型): 通常指的就是我们的数据模型。作用一般情况下用于封装数据(JavaBean)。
View(视图): 通常指的就是我们的 jsp 或者 html。作用一般就是展示数据的。 通常视图是依据模型数据创建的。
Controller(控制器): 是应用程序中处理用户交互的部分。作用一般就是处理程序逻辑的。 它相对于前两个不是很好理解,这里举个例子: 例如: 我们要保存一个用户的信息,该用户信息中包含了姓名,性别,年龄等等。 这时候表单输入要求年龄必须是 1~100 之间的整数。姓名和性别不能为空。并且把数据填充 到模型之中。 此时除了 js 的校验之外,服务器端也应该有数据准确性的校验,那么校验就是控制器的该做 的。 当校验失败后,由控制器负责把错误页面展示给使用者。 如果校验成功,也是控制器负责把数据填充到模型,并且调用业务层实现完整的业务需求。
1.2.4 SpringMVC 和 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 执行效率高。
入门案例:
步骤:1。启动服务器,加载一些配置文件
*DispatcherServlet对象创建
*SpringMVC.xml被加载
*HelloController创建成对象
2。发送请求,后天处理
SpringMVC框架基于组件方式执行流程
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。 使用mvc:annotation-driven 自动加载 RequestMappingHandlerMapping (处理映射器)和 RequestMappingHandlerAdapter ( 处 理 适 配 器 ) , 可 用 在 SpringMVC.xml 配 置 文 件 中 使 用 mvc:annotation-driven替代注解处理器和适配器的配置。
前端控制器(DispatcherServlet):控制整个流程的执行
处理器映射器(HandlerMapping):找到具体的哪个Controller类的哪个方法执行
处理器适配器(HandlerAdapter):帮助Controller类去实现方法
视图解析器(ViewResolver):调整到具体的页面
第三章:请求参数的绑定
- 请求参数的绑定说明
- 绑定机制
- 表单提交的数据都是k=v格式的 username=haha&password=123
- SpringMVC的参数绑定过程是把表单提交的请求参数,作为控制器中方法的参数进行绑定的
- 要求:提交表单的name和参数的名称是相同的
HTTP 协议,是一个无状态协议,即所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,
必须通过某种手段,让服务器端发生“状态转化”(State Transfer)。而这种转化是建立在表现层之上的,所以
就是 “表现层状态转化”。具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、
DELETE。它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。
- 拦截器的概述
- SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。 2. 可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链 中的拦截器会按着定义的顺序执行。 3. 拦截器和过滤器的功能比较类似,有区别 1. 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。 2. 拦截器是SpringMVC框架独有的。 3. 过滤器配置了/*,可以拦截任何资源。 4. 拦截器只会对控制器中的方法进行拦截。 4. 拦截器也是AOP思想的一种实现方式 5. 想要自定义拦截器,需要实现HandlerInterceptor接口。