三层架构
1、什么是三层架构?
- 在项目开发中,遵循的一种形式模式,分为三层
- 界面层:用啦接受客户端的输入,调用业务逻辑层进行功能处理,返回结果给客户端,过去的servlet就是界面层的功能
- 业务逻辑层:用来进行整个项目的业务逻辑处理,向上为界面层提供处理结果,向下问数据访问层要数据
- 数据访问层:专门用来进行数据库的增删查改操作,向上为业务逻辑层提供数据
- 各层之间的调用顺序是固定的,不允许跨层访问
- 界面层<—->业务逻辑层<—–>数据访问层
SSM框架
常用的况框架SSM
-
Spring:它是整合其他框架的框架,它的核心是IOC和AOP,它由20多个模块构成的,在很多领域都提供了很好的解决方案,是一个很强大的存在
-
SpriingMVC:他是Spring家族的医院,专门用来优化控制器(Servlet)的,提供了极其简单数据提交,数据携带,页面跳转等功能
-
MyBatis:是持久化层的一个框架,用来进行数据库的访问哟话,专注于SQL语句,极大的简化了JDBC的访问
什么是框架?
- 它是一个半成品软件,将所有的公共的,重复的功能解决掉,帮助程序员快速高效的进行开发,他是可复用的,可扩展的
什么是MyBatis框架
-
是Apache的一个开源项目iBatis
-
MyBaits最主要用来完成数据访问层的优化,它专注于sql语句,简化了过去JDBC繁琐的访问机制
-
添加框架的步骤
- 添加依赖
- 添加配置
- 创建ssm数据库,添加student表,并且插入数据
create database ssm default charset utf8;
use ssm;
create table student(
id int(11) AUTO_INCREMENT primary key,
name varchar(255) default null,
email varchar(255) default null,
age int(11) default null
) ENGINE=InnoDB default charset=utf8;
insert into student(name,email,age) values("张三",'zhangsan@126.com',22);
insert into student(name,email,age) values("李四",'lisi@126.com',21);
insert into student(name,email,age) values("王五",'wangwu@126.com',22);
insert into student(name,email,age) values("赵六",'zhaoliu@126.com',24);
select * from student;
配置SqlMapConfig.xml文件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0/EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--读取属性文件,jdbc.properties
属性:resource:从resource目录下找执行名称的文件加载
url:使用绝对路径加载属性文件
-->
<properties resource="jdbc.properties"></properties>
<!--配置数据库环境变量(数据库连接配置)-->
<!--environments里面可以配置多套environment
default:使用下面的environment标签的id属性进行执行配置
-->
<environments default="development">
<!--id:就是提供给environment的default属性是有-->
<environment id="development">
<!--配置事务管理器
type属性:指定事务管理的方式
JDBC:事务的控制交托给程序员处理
MANAGED:由容器(Spring)来管理事务
-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源
type:指定不同的配置方式
JNDI:Java命名目录接口,在服务器端进行数据库连接池的管理
POOLED:使用数据库连接池进行数据库的连接配置
UNPOLLED:不使用数据库连接池
-->
<dataSource type="POOLED">
<!--配置数据库连接的基本参数-->
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--注册mapper.xml文件
resource:从resource目录下找指定名称文件注册
url:使用绝对路径找文件注册
class:动态代理方式下的注册
-->
<mappers>
<mapper resource="StudentMapper.xml"></mapper>
</mappers>
</configuration>
配置StudentMapper.xml文件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0/EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
mapper:是整个文件的大标签,用来开始和结束xml
属性:
namespace:指定命名空间(相当于包名),用来区分不同mapper.xml文件中相同的id属性
-->
<mapper namespace="su">
<!--
完成查询全部学生的功能
resultType:指定查询返回结果集的类型,如果是集合,则必须是泛型的类型
parameterType:如果有参数,则通过它来指定参数类型
-->
<select id="getAll" resultType="com.bjpowernode.pojo.Student">
select id,name,email,age
from student
</select>
</mapper>
测试
//使用文件流的读取核心配置文件SqlMapConfig
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出SqlSession的对象
SqlSession sqlSession = factory.openSession();
//完成查询操作
List<Student> list = sqlSession.selectList("su.getAll");
list.forEach(student -> System.out.println(student));
//关闭SqlSession
sqlSession.close();
使用mybatis进行增删查改
SqlMapConfig配置文件
<!--按主键id查询学生信息-->
<select id="getById" parameterType="int" resultType="com.bjpowernode.pojo.Student">
select id,name,email,age from student where id=#{id}
</select>
<!--按学生名称模糊查询-->
<select id="getByName" parameterType="string" resultType="com.bjpowernode.pojo.Student">
select id,name,email,age from student where name like '%${name}%'
</select>
<!--增加学生-->
<insert id="insert" parameterType="com.bjpowernode.pojo.Student">
insert into student(name,email,age) values (#{name},#{email},#{age})
</insert>
<!--按主键删除学生-->
<delete id="delete" parameterType="int">
delete from student where id = #{id}
</delete>
<!--修改学生-->
<update id="update" parameterType="com.bjpowernode.pojo.Student">
update student set name=#{name},email=#{email},age=#{age} where id = #{id}
</update>
测试程序
//读取核心配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//取出SqlSession
SqlSession sqlSession = factory.openSession();
//完成查询操作
List<Student> list = sqlSession.selectList("su.getAll");
list.forEach(student -> System.out.println(student));
/*for (Student x : list){
System.out.println(x);
}*/
//关闭SqlSession
sqlSession.close();
//按主键查询学生
Student stu = sqlSession.selectOne("su.getById",1);
System.out.println(stu);
//关闭SqlSession
sqlSession.close();
//执行模糊查询
List<Student> list = sqlSession.selectList("su.getByName","李");
for (Student x : list){
System.out.println(x);
}
//list.forEach(student -> System.out.println(student));
//关闭SqlSession
sqlSession.close();
//增加学生
int num = sqlSession.insert("su.insert", new Student("苏叶", "210469@qq.com", 21));
sqlSession.commit();
sqlSession.close();
//删除学生
int delete = sqlSession.delete("su.delete", 5);
sqlSession.commit();
sqlSession.close();
//修改学生
int update = sqlSession.update("su.update", new Student(2, "王麻子", "wm@126.com", 18));
sqlSession.commit();
sqlSession.close();
注意:执行增删改语句一定要提交事务
Mybatis对象分析
1、Resources类
- 就是解析SqlMapConfig.xml文件,创建出相应的对象
- InputStream in = Resources.getResourceAsStream(“SqlMapConfig.xml”);
2、SqlSessionFactoryBuilder接口
- 使用ctrl+h查看本接口的子接口及实现类
- DefaultSqlSessionFactory是实现类
- SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
3、SqlSession接口
- DefaultSqlSession实现类
- SqlSession sqlSession = factory.openSession();
为实体类注册别名
1、单个注册
在SqlMapConfig.xml文件中properties 标签后 environments 前面加入标签,注册之后就可以在Mapper.xml文件中的resultType属性或者parameterType属性中使用这个别名
<!--注册实体类的别名-->
<typeAliases>
<!--单个注册-->
<typeAlias type="com.bjpowernode.pojo.Student" alias="student"></typeAlias>
</typeAliases>
2、批量注册
<!--批量注册别名
别名是类名的驼峰命名规范
-->
<package name="com.bjpowernode.pojo"/>
批量把这个目录下的所有实体类都注册别名,别名是类名的驼峰命名规范
输出日志:固定的写法
<!--设置日志输出底层执行的代码-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
动态代理
1、动态代理存在的意义
- 在三层架构中,业务逻辑层需要通过接口访问数据访问层的功能,可以使用动态代理
2、动态代理的实现规范
- UserMapper.xml文件与UserMapper.java的接口必须在同一个目录。
- UserMapper.xml文件与UserMapper.java接口的文件名必须一致,后缀可以不管
- UserMapper.xml文件中的标签id的值与UserMapper.java的接口中方法名称完全一致
- UserMapper.xml文件中的标签的parameterType属性值与UserMapper.java的接口中方法参数类型完全一致
- UserMapper.xml文件中标签的resultType值与UserMapper.java的接口方法的返回值类型必须一致
- UserMapper.xml文件中标签的namespace属性必须是接口的完全限定名称,例如com.bjpowernode.mapper.UserMapper
- 在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名称,例如com.bjpowernode.mapper.UserMapper
3、动态代理的访问
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
sqlSession = factory.openSession();
uMapper = sqlSession.getMapper(UsersMapper.class);
//直接调用方法
List<Users> list = uMapper.getAll();
for(Users x : list){
System.out.println(x);
}
}
//
//原来的方式
public void testGetAll() throws IOException {
//完成查询操作
//只能通过namespace标注下id的值来调用方法,而且不同的语句使用不同的sqlsession方法
List<Student> list = sqlSession.selectList("su.getAll");
list.forEach(student -> System.out.println(student));
}
使用动态代理的方式进行获取数据
#{}和${}的区别
#{}占位符
-
传参的时候大部分使用#{}传参,它的底层使用的是PreparedStatement对象,是安全的数据库访问,防止sql注入
-
#{}里面写什么东西,主要看parameterType参数的类型
- 如果parameterType的类型是简单类型(8种基本(封装)+String),则#{}里面随便写
<select id="getById" resultType="users" parameterType="int"> >入参类型是简单类型
select id,username,birthday,sex,address from users where id = #{id} >这个里面可以随便写
</select>
- parameterType的类型如果是实体类型,则#{}里面只能是类中的成员变量的名称,并且区分大小写
<insert id="add" parameterType="users">
insert into users(username,birthday,sex,address) values(#{userName},#{birthday},#{sex},#{address})
</insert>
${}字符串拼接或字符串替换
-
字符串拼接,一般用于模糊查询中,建议少用,因为有sql注入的风险
-
也分为两种情况,同样看parameterType类型
- 如果parameterType类型是简单类型,则${}里面随便写,但是分版本,如果是3.5.1及以下版本,只能写value
<select id="getByLike" parameterType="string" resultType="users"> >传参是简单类型
select id,username,birthday,sex,address from users
where username like '%${userName}%' >${}里面可以随便写
</select>
- parameterType的类型如果是实体类型,则${}里面只能是类中的成员变量的名称(溴铵在已经很少用了)
优化模糊查询,因为直接使用${}这个进行拼接会有sql注入危险,所以以后我们使用concat()函数来进行模糊查询
<select id="getByNameGood" parameterType="string" resultType="users">
select id,username,birthday,sex,address from users where username like concat('%',#{name},'%')
</select>
字符串替换使用${}
在有的时候我们需要进行模糊查询的时候,不仅仅是根据用户名,有的时候根据地址进行模糊查询,这个时候我们肯定是不能写两个不同的sql语句,这样太麻烦了,所以这个时候我们就要需要用到注解
List<Users> getByNameOrAddress(
@Param("columnName") String columnName,
@Param("columnValue") String columnValue);
<!---->
<!--如果参数超过一个则parameterType不用写-->
<select id="getByNameOrAddress" resultType="users">
select id,username,birthday,sex,address from users where
${columnName} like concat('%',#{columnValue},'%')
</select>
然后进行测试,要查询哪个字段,就靠自己传参
@Test
public void testGetByNameGoodOrAddress(){
List<Users> list = uMapper.getByNameOrAddress("address","南");
for (Users x:list){
System.out.println(x);
}
}
返回主键业务
假如在有的时候,我们插入一行数据,但是主键是自增的,并且关联了其他表,且也要为其增加数据,这样我们还要先查询自增的主键是多少再进行增加另一个表的数据,这样很麻烦,所以我们可以在插入数据的时候返回主键
<insert id="add" parameterType="users">
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into users(username,birthday,sex,address) values(#{userName},#{birthday},#{sex},#{address})
</insert>
- selectKey标签的详解:
- keyProperty:users对象的哪个属性来返回主键值
- resultType:返回主键的类型
- order:在插入语句前还是后返回主键的值
动态SQL
-
什么是动态sql
- 可以定义代码片段,可以进行逻辑判断,可以进行循环处理(批量处理),使条件判断更简单
-
:用来定义代码片段,可以将所有的列名,或者复杂的条件定义为代码片段,供使用时调用
-
:用来引用标签定义的代码片段
<!--定义代码片段-->
<sql id="allColumns">
id,username,birthday,sex,address
</sql>
<!--在sql语句中引用-->
<select id="getAll" resultType="users">
select <include refid="allColumns"></include> from users
</select>
-
进行条件判断
-
进行多条件拼接,在查询,删除,更新中使用
- 强化select查询,使用if进行判断
<select id="getByCondition" parameterType="users" resultType="users">
select <include refid="allColumns"></include> from users
<where>
<if test="userName != null and userName !=''">
and username like concat('%',#{userName},'%')
</if>
<if test="birthday != null">
and birthday = #{birthday}
</if>
<if test="sex != null and sex != ''">
and sex = #{sex}
</if>
<if test="address != null and address != ''">
and address like concat('%',#{address},'%')
</if>
</where>
</select>
测试
@Test
public void testGetByCondition(){
Users users = new Users();
users.setUserName("小");
List<Users> list = uMapper.getByCondition(users);
for (Users x : list){
System.out.println(x);
}
}
- :有选择的进行更新处理,至少更新一列
<update id="updateBySet" parameterType="users" >
update users
<set>
<if test="userName != null and userName != ''">
username = #{userName},
</if>
<if test="birthday != null">
birthday = #{birthday},
</if>
<if test="sex != null and sex != ''">
sex = #{sex},
</if>
<if test="address != null and address != ''">
address = #{address},
</if>
</set>
where id = #{id}
</update>
测试
@Test
public void testUpdateBySet(){
Users users = new Users();
users.setId(6);
users.setUserName("TheShy");
int i = uMapper.updateBySet(users);
sqlSession.commit();
}
:用来进行循环遍历,完成循环条件查询,批量删除,批量增减,批量更新
-
collection:用来指定入参的类型,如果是List集合则为小写的list,Map为map,数组Array是array
-
item:每次循环遍历出来的值或者对象
-
separator:多个值或者多个对象之间的分隔符
-
open:整个循环前的前括号
-
close:整个循环的后括号
<select id="getByIds" resultType="users">
select <include refid="allColumns"></include>
from users
where id in (
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
)
</select>
使用foreach实现批量删除
<delete id="deleteBatch">
delete from users
where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
测试
@Test
public void testDeleteBatch(){
Integer[] array = {4,7};
int i = uMapper.deleteBatch(array);
sqlSession.commit();
}
注意:如果要使用批量更新,需要再properties配置文件中的url后加上 &allowMultiQueries=true
指定参数位置
如果入参是多个,可以通过指定参数位置进行传参,是实体类包含不住的条件,实体类只能封装住成员变量的条件,如果某个成员变量需要有区间范围之内的判断,或者两个值进行处理,则实体类包不住
- 例如:查询指定日期范围内的用户信息
- 首先定义接口中的方法,因为是在一个区间之内,所以需要两个参数的方法
List<Users> getByBirthday(Date beigin,Date end);
- 然后在Mapper.xml文件中编写sql语句
<select id="getByBirthday" resultType="users">
select <include refid="allColumns"></include>
from users
where birthday between #{arg0} and #{arg1}
</select>
在进行多个参数查询的时候,我们可以用 arg0 开始指定,从零开始,依次递增
- 测试
@Test
public void testGetByBirthday() throws ParseException {
Date begin = sf.parse("2001-01-1");
Date end = sf.parse("2003-01-1");
List<Users> byBirthday = uMapper.getByBirthday(begin, end);
for (Users x : byBirthday){
System.out.println(x);
}
}
入参为Map
- 如果入参超过一个以上,使用map封装查询条件,更有语义,查询条件更明确
List<Users> getByMap(Map map);
<!--
#{birthdayBegin} 为map集合的key
#{birthdayEnd} 为map集合的value
-->
<select id="getByMap" resultType="users">
select <include refid="allColumns"></include>
from users
where birthday between #{birthdayBegin} and #{birthdayEnd}
</select>
@Test
public void testGetByMap() throws ParseException {
Date begin = sf.parse("2001-01-1");
Date end = sf.parse("2003-01-1");
Map map = new HashMap<>();
map.put("birthdayBegin",begin);
map.put("birthdayEnd",end);
List<Users> byBirthday = uMapper.getByMap(map);
for (Users x : byBirthday){
System.out.println(x);
}
}
返回值是map
- 如果返回的数据实体类无法包含,可以使用map返回多张表中若干的数据,然后后这些数据之间没有任何关系,就是Object类型,map的key就是列名或者别名
//返回值是map(一行)
Map getReturnMap(Integer id);
//返回值为多行map
List<Map> getAllMap();
<!--返回值为一行map-->
<select id="getReturnMap" parameterType="int" resultType="map">
select username,address from users
where id = #{id}
</select>
<!--返回值为多行map-->
<select id="getAllMap" resultType="map">
select username,address from users
</select>
//返回值为一行map
@Test
public void testGetReturnMap(){
Map returnMap = uMapper.getReturnMap(2);
System.out.println(returnMap.get("username"));
System.out.println(returnMap.get("address"));
}
//返回多行Map
@Test
public void testGetAllMap(){
List<Map> allMap = uMapper.getAllMap();
for (Map x : allMap){
System.out.println(x);
}
}
resultMap的用法
- 在有的时候实体类的属性和数据库表的字段不一样的情况下,可以使用resultMap进行关系映射
<!--实体类和表中的字段不一样的情况下,可以使用resultMap手工完成映射-->
<resultMap id="bookmap" type="book">
<!--
id是你在sql语句中设置的id type是你映射的类型
-->
<!--主键绑定-->
<!--property为你实体类中的属性,column为你数据库中对应的字段名-->
<id property="id" column="bookid"></id>
<!--非主键绑定-->
<result property="name" column="bookname"></result>
</resultMap>
<select id="getAllBook" resultMap="bookmap"> <!--设置返回值为resultMap,并且设置名字,为你resultMap中的id-->
select bookid,bookname from book
</select>
表的关联关系
关联关系是有方向的
-
一对多关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在老师方向,就是一对多关联
-
多对一关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在学生的方向,这就是多对一的关系
-
一对一关联:一个老师辅导一个学生,一个学生只请教一个老师,学生和老师一对一
-
多对多关联:园区划线的车位和园区的任意一辆车,一个车位可以停任意一辆车,任意一辆车可以停在任意的一个车位。
-
一对多的关联管理
- 客户和订单就是典型的一对多关联关系,一个客户名下可以有多个订单,客户表是一方,订单表是多方
-
使用一对多的关联关系,可以按组查询客户的同时查询该客名下所有的订单
- 把两个表创建实体类,客户表可以创建一个订单类型的属性
//客户表
private Integer id;
private String name;
private Integer age;
private List<Orders> ordersList;
//订单orders表
private Integer id;
private String orderNumber;
private Double orderPrice;
因为返回的数据不仅仅有客户表中的信息,还有订单表中的信息,所以数据库要多表连接查询,查询的数据的不同,所以要使用resultMap进行一个绑定
<resultMap id="customermap" type="customer">
<id property="id" column="cid"></id>
<result property="name" column="name"></result>
<result property="age" column="age"></result>
<!--orders属性绑定-->
<collection property="ordersList" ofType="orders">
<id property="id" column="oid"></id>
<result property="orderNumber" column="orderNumber"></result>
<result property="orderPrice" column="orderPrice"></result>
</collection>
</resultMap>
<select id="getById" parameterType="int" resultMap="customermap">
select c.id cid,name,age,o.id oid,orderNumber,orderPrice,customer_id
from customer c left join orders o on c.id = o.customer_id
where c.id = #{id}
</select>
多对一关联关系:订单和客户就是多对一关联,站在订单的方向查询同时将客户信息查询出来这就是多对一查询,同理可以在订单的实体类中设置用户customer为属性,然后创建订单的接口和其Mapper.xml文件
总结:无论什么关联关系,如果某一方持有另一方的集合,则使用标签完成映射,如果某方持有另一方对象,则使用标签完成映射
事务
多个操作同时完成,或者同时失败成为事务处理
- 事务有四个特性:一致性,持久性,原子性,隔离性
- 在mybatis中是设置事务,设置为程序员自己处理的提交和回滚
<transactionManager type="JDBC"></transactionManager>
- 也就可以设置为自动提交
sqlSession = factory.openSession(); //默认是手工提交事务,设置为false也是手动提交事务,设置为true为自动提交事务
sqlSession = factory.openSession(true);//设置为自动提交,在增删改后面不需要commit()
缓存
- mybatis框架提供了两级缓存,一级缓存和二级缓存,默认开启一级缓存
- 缓存就是为了提高查询效率,如果数据库中发生改变,那么将会清空缓存
- 使用缓存后查询的流程:
- 查询时先到缓存里面查,如果没有则去数据库查询,放在缓存一份,然后返回给客户端,下次再查询的时候,不再访问数据库,直接在缓存里面拿。如果数据库发生了commit()操作,那么将会清空缓存。
- 一级缓存,使用的是sqlSession的作用域,同一个sqlSession共享一级缓存的数据
- 二级缓存使用的是mapper的作用域,不同的sqlSession只要访问的是同一个mapper.xml文件,则共享二级缓存作用域
ORM
- ORM(Object Relational Mapping):对象映射关系
- mybatis框架是ORM非常优秀的框架
- Java语言中以对象的方式操作数据,存到数据库中是以表的方式进行存储,对象中的成员变量与表中的列之间的数据互换成为映射,整个这套操作就是ORM
- 持久化的操作:将对象保存到关系型数据库中,将关系型数据库中的数据读出来以对象的形式封装
- mybatis是持久化层优秀的框架