软件开发常用结构
三层架构
三层架构包含的三层:
界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)
三层的职责:
- 界面层(表示层,视图层):主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和 用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。 主要是和用户打交道,接受用户的的请求参数,显示处理结果的。(jsp,html,servlet)
- 业务逻辑层:接收界面层传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
- 数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交 给业务层,同时将业务层处理的数据保存到数据库.
三层对应的包:
界面层:controller包(servlet...)
业务逻辑层:service包
数据访问层:dao包
三层中类的交互流程:
用户---> 界面层--->业务逻辑层--->数据访问层(持久层)--->DB 数据库(mysql)
三层架构对应的框架:
界面层------------>servelt---------->springmvc(框架)
业务逻辑层------>service类------>spring(框架)
数据访问层------->dao类--------->mybatis(框架)
框架
框架定义
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方 法;另一种认为,框架是可被应用开发者定制的应用骨架、模板。
简单的说,框架其实是半成品软件,就是一组组件,供你使用完成你自己的系统。它定义好了一些基础功能,需要加入自己的功能就是完整的,基础功能是可重复使用的,可升级的。从另一个角度来 说框架一个舞台,你在舞台上做表演。在框架基础上加入你要完成的功能。
框架特点:
1)框架一般不是全能的,不能做所有的事情
2)框架是针对某一个领域有效,特长是在某一个方面,比如mybatis做数据库操作强,但是他不能做其他的
3)框架是一个软件
使用JDBC的缺陷:
1. 代码比较多,开发效率低
2.需要关注 Connection ,Statement, ResultSet 对象创建和销毁
3.对 ResultSet 查询的结果,需要自己封装为 List
4.重复的代码比较多些
5.业务代码和数据库的操作混在一起
MyBatis框架
一个持久层框架,早期叫做ibatis,mybatis是MyBatis SQL Mapper FrameWork for Java(sql映射框架),MyBatis可以操作数据库,对数据进行增删改查,Mybatis可以看做是高级的jdbc,解决jdbc的缺点。
1)sql mapper : sql 映射
可以吧数据库中的一行数据,映射成一个java对象,一行数据可以看做是一个java对象。操作这个对象就相当于操作表中的数据。
2)Data Access Object (DAOs):数据访问
对数据库执行增删改查。
mybatis提供的功能:
0.注册驱动
1.提供了创建Connection,statement,Resultset的能力,不用开发人员创建这个对象了
2.提供了执行sql语句的能力
3.提供了循环sql,把sql结果转换为java对象,List集合的能力。
4.提供了关闭资源的能力,不用自己关闭connection,statement,resultset
开发人员需要做的是:提供sql语句。
流程:开发人员提供sql语句----->mybatis处理sql----->开发人员得到List集合或者java对象(表中的数据)。
总结:
mybatis是一个sql映射框架,提供的数据库的操作能力,增强的jdbc,使用mybatis让开发人员集中精神写sql就行了,不必关心Connection,Statement,ResultSet的创建,销毁,SQL的执行。
MyBatis入门:第一个MyBatis的创建
实现步骤:
1.创建student表(id,name,email,age)
2.新建maven项目
3.修改pom.xml文件
1)加入依赖:MyBatis依赖,mysql依赖,junit依赖
2)在<build>中加入资源
4.创建实体类Student,定义属性,属性要和student表中的列名保持一致
5.创建Dao接口,定义操作数据库的方法
6.创建xml文件(mapper文件),写sql语句
mybatis框架推荐是把sql语句和java代码分开
mapper文件:定义和dao接口在同一目录,一个表一个mapper文件。
7.创建mybatis的主配置文件(xml文件),有一个,放在resources目录下
1)定义创建连接实例的数据源(DataSource)对象
2)指定其他mapper文件的位置。
8.创建测试的内容
1)可以使用main方法,测试MyBatis访问数据库
2)也可以使用junit访问数据库
概念
1.自动提交:当你的sql语句执行完毕后,提交事务,数据更新操作直接保存到数据库
2.手动提交事务:在需要提交事务的位置,执行方法,提交事务或者回滚事务。
主要的类的介绍
1.Resources:mybatis中的一个类,负责读取主配置文件信息
InputStream in= Resources.getResourceAsStream(config);
2.SqlSessionFactoryBuilder:创建SqlSessionFactory对象,
SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder();
//创建SqlSessionFactory对象
SqlSessionFactory factory=builder.build(in);
3.SqlSessionFactory:重量级对象,程序创建这个对象耗时比较长,使用资源比较多,在整个项目中,有一个就够了。
SqlSessionFactory:接口,接口实现类:DefaultSqlSessionFactory
SqlSessionFactory作用:获取SqlSession对象,SqlSession sqlSession=factory.openSession();
openSession()方法说明:
1)openSession():无参数的,获取的是非自动提交事务的SqlSession对象
2)openSession(Boolean):openSession(true) 获取自动提交事务的SqlSession
openSession(false)非自动提交事务的SqlSession对象。
4.SqlSession:是一个接口,定义了操作数据的方法,例如selectOne(),selectList(),insert(),update(),delete(),commit(),rollback()。
SqlSession接口的实现类DefaultSqlSession
SqlSession使用要求:SqlSession对象不是线程安全的,需要在方法内部使用,在执行sql语句之前,使用openSession()获取SqlSession对象。在执行完sql语句后,需要关闭它,执行SqlSession.close(),这样能保证他的使用是线程安全的。
SqlSession使用步骤:
1.在方法的内部,执行sql语句之前,先获取sqlSession对象
2.调用SqlSession的方法,执行sql语句
3.关闭SqlSession对象,执行SqlSession.close()
SqlSession的作用是提供了大量的执行sql语句的方法:
selectOne:执行sql语句,最多得到一行记录,多余一行是错误的
selectList:执行sql语句,返回多行数据
selectMap:执行sql语句,得到一个Map结果
insert:执行insert语句
update:执行update语句
delete:执行删除语句
commit:提交事务
rollback:回滚事务
dao代理
mybatis提供代理:mybatis创建Dao接口的实现类对象,完成sql语句的执行。mybatis创建一个对象代替你自己的dao实现类功能。
使用mybatis代理要求:
1)mapper文件中的namespace一定是dao接口的全限定名称
2)mapper文件中标签的id是dao接口方法的名称。
mybatis代理实现方式:使用SqlSession对象的方法getMapper(dao.class)
例如:现在有StudentDao接口,接口中有方法selectStudentById,相关代码为:
SqlSession sqlSession=MyBatisUtils.getSession();//MyBtisUtils.getSession()方法为自己写的工具类:获取sqlSession方法的工具类
StudentDao dao=sqlSession.getMapper(StudentDao.class);
Student student=dao.selectStudentById(1001);//1001为学生学号
理解参数
理解参数:通过java程序把数据传入到mapper文件中的sql语句,参数主要是指dao接口方法的形参。
parameterType
parameterType表示的是参数的类型,指定dao方法的形参数据类型。这个属性值可以使用java类型的全限定名称或者mybatis定义的别名
dao接口方法是一个简单类型的参数
简单类型:包括java基本数据类型和String
Student selectStudentByEmail(String email);
<!--mapper文件获取这个参数值,使用#{任意字符}-->
<select id="selectStudentByEmail" resultType="com.hao.entity.Student">
select id,name,email,age from student where email=#{email}
</select>
dao接口方法有多个简单类型的参数
@param:命名参数,在方法的形参前面使用的,定义参数名,这个名称可以用在mapper文件中。
dao接口,方法的定义
/*
多个简单类型的参数
使用@param命名参数,注解是mybatis提供的
使用位置:在形参定义的前面
属性:value自定义的参数名称(不过自定义的名字要与mapper文件中的变量一样)
*/
List<Student> selectStudentsByNameOrAge(@Param("myname") String name, @Param("myage") int age);
<!--当使用了@Param命名后,例如@Param("myname"),则在mapper中使用#{命名的参数},name=#{myname},即@Param中的参数值与name里面的参数值一致-->
<select id="selectStudentsByNameOrAge" resultType="com.hao.entity.Student">
select id,name,email,age from student where name=#{myname} or age=#{myage}
</select>
使用对象作为参数
方法的形参是一个java对象,这个java对象表示多个参数。使用对象的属性值作为参数使用
java对象的定义:
public class Student {
private int id;
private String name;
private String email;
private int age;
具有set和get方法;
}
dao接口:
//按照对象查询学生
List<Student> selectStudentsByObject(Student student);
mapper文件:
<!--
一个java对象作为方法的参数,使用对象的属性作为参数值使用
简单的语法:#{属性名},mybatis调用此属性的get方法获取属性值。
-->
<select id="selectStudentsByObject" resultType="com.hao.entity.Student">
select id,name,email,age from student where name=#{name} or age=#{age}
</select>
任意对象都可以使用,只要有set方法
@Test
public void selectStudentsByObject(){
SqlSession sqlSession=MyBatisUtils.getSqlSession();
StudentDao dao=sqlSession.getMapper(StudentDao.class);
Student student=new Student();
student.setAge(18);
student.setName("李白");
List<Student> students=dao.selectStudentsByObject(student);
students.forEach(stu-> System.out.println(stu));
sqlSession.close();
}
#和$的区别
#占位符
语法:#{字符}
mybatis处理#{} ,在jdbc中使用的对象是PrepareStatement对象
#{}的特点:
1)使用的PrepareStatement对象,执行sql语句,效率高。
2)使用的PrepareStatement对象,能避免sql语句,SQL语句执行的更安全。
3)#{}常常作为列值使用的,位于等号的右侧,#{}位置的值和数据类型有关的。
$占位符
语法:${字符}
myvatis执行${}占位符的sql语句,在jdbc中相当于使用Statement对象
${}的特点:
1)使用Statement对象执行sql语句,效率低
2)${}占位符的值,使用字符串连接方式,有sql注入的风险,有代码安全的问题
3)${}数据是原样使用的,不会区分数据类型
4)${}常用作表名或者列名,在能保证数据安全的情况下使用${}
封装MyBatis输出结果
封装输出结果:MyBatis执行sql语句,得到Resultset,转为java对象
resultType
resultType属性:在执行select时使用,作为<select>标签的属性出现的
resultType:表示结果类型,mysql执行sql语句后得到的java对象类型。
1.resultType表示简单类型:
dao方法:
long countStudent();
mapper文件:
<!--执行sql语句,得到的是一个值(一行一列)-->
<select id="countStudent" resultType="java.lang.Long">
select count(*) from student
</select>
2.resultType表示map结构
执行sql得到一个Map数据结构,mybatis执行sql,把ResultSet转为map,
sql执行结果,列名做map的key,列值作为value
sql执行的是一行记录,转为map结构是正确的。
dao接口返回是一个map,sql语句最多能获取一行,多余一行就是错误的
Map<Object,Object> selectMap(@Param("stuid") Integer id);
<select id="selectMap" resultType="java.util.Map">
select * from student where id=#{stuid}
</select>
resultType:使用的是java类型的全限定名称,表示的意思是mybatis执行sql,把ResultSet中的数据转为Student类型的对象,MyBatis会做以下的操作:
1:调用com.hao.entity.Student的无参构造方法,创建对象。
Student student =new Student();
2.同名的列赋值给同名的属性
student.setId(rs.getInt("id"));
student.setName(rs.getName("name"));
3.得到java对象,如果dao接口返回的是List集合,mybatis把student对象放入到List集合。
resultMap
resultMap:结果映射,自定义列名和java对象属性的对应关系,常用在列名和属性名不同的情况。
用法:
1.先定义resultMap标签,指定列名和属性名称对应关系
2.在select标签使用resultMap属性,指定上面定义的resultMap的id值
<!--使用resultMap定义列和属性的关系-->
<!--
定义resultMap
id:给resultMap的映射关系起个名称,唯一值
type:java类型的全限定名称
-->
<resultMap id="aaa" type="com.hao.entity.Student">
<!--定义列名和属性名相对应的值-->
<!--主键类型使用id标签-->
<id column="id" property="id"/>
<!--非主键类型使用result标签-->
<!--列名和属性名相同可以不用配置,也可以配置-->
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="age" property="age"/>
</resultMap>
<!--使用resultMap属性,指定映射关系的id-->
<select id="selectStudentsByObject2" resultMap="aaa">
select id,name,email,age from student where name=#{name} or age=#{age}
</select>
MyBatis自定义别名
mybatis提供的对java类型定义简短,好记名称
自定义别名的步骤:
1)在mybatis主配置文件中,使用typeAliases标签声明别名
2)在mapper文件中,resultType=“别名”
3)声明别名,在主配置文件中声明别名。
第一种语法格式:
type:java类型的全限定名称(自定义类型)
alias:自定义别名
优点:别名可以自定义
缺点:每个类型必须单独定义
<typeAliases>
<typeAlias type="com.hao.entity.Student" alias="stu" />
</typeAliases>
第二种语法格式:
name:包名,mybatis会把这个包中所有类名作为别名(不用区分大小写)
优点:使用方便,一次给多个类定义别名
缺点:别名不能自定义,必须是类名,并且如果不同的包下有相同的类名,可能会出现一些错误
<typeAliases>
<package name="com.hao.entity" />
</typeAliases>
数据库的列名和java对象属性名称不一样的解决方式
1)使用resultMap:自定义列名和属性名
2)使用resultType:使用列别名,让别名和java对象属性一样
动态sql动态sql:同一个dao的方法,根据不同的条件可以表示不同的sql语句,主要是where部分有变化。
主要通过使用mybatis提供的标签,实现动态sql的能力,主要学习if,where,foreach,sql,使用动态sql的时候,dao方法的形参使用java对象。
什么情况下使用动态sql:多条件查询的时候使用动态sql。
if标签
语法格式:
<if test="boolean判断结果">
sql代码
</if>
<!--多个if可以同时存在-->
在mapper文件中:
<select id="selectStudent" resultType="com.hao.entity.Student">
<!--主sql-->
select * from student
<!--部分sql,如果部分sql满足条件,将会把部分sql加入到主sql当中执行。-->
<if test="条件">
sql语句
</if>
</select>
示例:
List<Student> selectIf(Student student);
<select id="selectIf" resultType="com.hao.entity.Student">
select * from student
<!--为了使语句严谨,要加上1=-1.....-->
where 1=-1
<if test="name!=null and name!=''">
or name=#{name}
</if>
<if test="age>0">
or age=#{age}
<!--如果用大于号或者小于号要用实体类符号代替-->
</if>
</select>
Test
@Test
public void testSelectIf(){
SqlSession sqlSession=MyBatisUtils.getSqlSession();
StudentDao dao=sqlSession.getMapper(StudentDao.class);
Student student=new Student();
//student.setName("凯");
student.setAge(18);
List<Student> list=dao.selectIf(student);
list.forEach(stu-> System.out.println(stu));
sqlSession.close();
}
where标签
使用if标签时,容易引起sql语句语法错误,使用where标签解决if产生的语法问题,
使用where标签时,里面是一个或者多个if标签,当有一个if标签判断条件为true,where标签会转为WHERE关键字附加到sql语句的后面,如果if没有一个条件为true,忽略where和里面的if。
语法规则:
<where>
<if test="条件1">sql语句1</if>
<if test="条件2">sql语句2</if>
</where>
<select id="selectWhere" resultType="com.hao.entity.Student">
select * from student
<where>
<if test="name!=null and name!=''">or name=#{name}</if>
<if test="age>0">or age>#{age}</if>
</where>
</select>
foreach
使用foreach可以循环数组,list集合,一般使用在in语句中。
语法格式:
<foreach collection="集合类型" open="开始的字符" close="结束的字符" item="集合中的成员" separator="集合成员之间的分隔符">
#{item的值}
</foreach>
<!--
标签属性:
collection:表示循环的对象是数组还是list集合。如果dao接口方法的形参是数组,collection="array",如果dao接口形参是List,collection="list"
open:循环开始时的字符
close:循环结束时的字符
item:集合成员,自定义变量
separator:集合成员之间的分隔符
#{item}:获取集合成员的值
-->
foreach第一种方式,循环简单类型的List
dao接口:
List<Student> selectForeachOne(List<Integer> idlist);
映射文件;
<select id="selectForeachOne" resultType="com.hao.entity.Student">
select * from student where id in
<foreach collection="list" open="(" close=")" separator="," item="myid">
#{myid}
</foreach>
</select>
测试文件:
@Test
public void testSelectForeachOne(){
SqlSession sqlSession=MyBatisUtils.getSqlSession();
StudentDao dao=sqlSession.getMapper(StudentDao.class);
List<Integer> idlist=new ArrayList<>();
idlist.add(1001);
idlist.add(1002);
idlist.add(1003);
List<Student> students=dao.selectForeachOne(idlist);
students.forEach(stu-> System.out.println(stu));
sqlSession.close();
}
foreach第一种方式,循环对象类型的List
dao接口:
List<Student> selectForeachTwo(List<Student> studentList);
映射文件:
<select id="selectForeachTwo" resultType="com.hao.entity.Student">
select * from student
<if test="list!=null and list.size>0">
where id in
<foreach collection="list" open="(" close=")" separator="," item="stu">
#{stu.id}
</foreach>
</if>
</select>
测试文件:
@Test
public void testSelectForeachTwo(){
SqlSession sqlSession=MyBatisUtils.getSqlSession();
StudentDao dao=sqlSession.getMapper(StudentDao.class);
List<Student> stuList=new ArrayList<>();
Student stu1=new Student();
stu1.setId(1001);
Student stu2=new Student();
stu2.setId(1002);
stuList.add(stu1);
stuList.add(stu2);
List<Student> students=dao.selectForeachTwo(stuList);
students.forEach(stu-> System.out.println(stu));
sqlSession.close();
}
sql标签:代码片段
sql标签表示一段sql代码,可以是表名,几个字段,where条件都可以,可以在其他地方复用sql标签的内容,
使用方式:
1)在mapper文件中定义sql代码片段<sql id="唯一字符串"> 部分sql语句</sql>
2)在其他的位置,使用include标签引用某个代码片段<include refid="代码片段的id" />
mybatis配置文件分为两大类:
1)mybatis主配置文件:
提供mybatis全局设置的,包含的内容有:日志,数据源,mapper文件的位置。
2)mybatis的mapper文件。
写sql语句的,一般是一个表一个mapper文件。
settings部分
settings是mybatis的全局设置,影响整个mybatis的运行,这个设置一般使用默认值就可以了。
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
typeAliase别名
第一种语法格式:
type:java类型的全限定名称(自定义类型)
alias:自定义别名
优点:别名可以自定义
缺点:每个类型必须单独定义
<typeAliases>
<typeAlias type="com.hao.entity.Student" alias="stu" />
</typeAliases>
第二种语法格式:
name:包名,mybatis会把这个包中所有类名作为别名(不用区分大小写)
优点:使用方便,一次给多个类定义别名
缺点:别名不能自定义,必须是类名,并且如果不同的包下有相同的类名,可能会出现一些错误
<typeAliases>
<package name="com.hao.entity" />
</typeAliases>
配置环境
environments:环境标签,在它里面可以配置多个environment
属性:default,必须是某个environment的id属性值,表示mybatis默认连接的数据库。
environment:表示一个数据库的连接信息。
属性:id自定义的环境标识,唯一值
transactionManager:事务管理器
属性:type表示事务管理器的类型。
属性值:1)JDBC:使用Connection对象,由mybatis自己完成事务的处理。
2)MANAGED:管理,表示把事务的处理交给容器实现(由其他软件完成事务的提交,回滚)
dataSource:数据源,创建Connection对象,连接数据库。
属性:type数据源的类型
属性值:1)POOLED,mybatis会在内存中创建PooledDataSource类,管理多个Connection连接对象,使用的连接池。
2)UNPOOLED,不使用连接池,mybatis创建一个UnPooledDataSouurce这个类,每次执行sql语句先创建Connection对 象,再执行sql语句,最后关闭连接。
3)JNDI,java的命名和目录服务
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/springdb"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
使用数据库属性配置文件
需要把数据库的配置信息放到一个单独文件中,独立管理,这个文件扩展名是properties.在这个文件中,使用自定义的key=value的格式表示数据。
使用步骤:
1.在resources目录中,创建xxxx.properties
2.在文件中,使用key=value的格式定义数据
例如:jdbc.url=jdbc:mysql://localhost:3306/springdb
3.在mybatis主配置文件,使用property标签引用外部的属性配置文件
语法:
<properties resource="properties文件路径"/>
4.在使用值的位置,使用${key}获取key对应的value(等号右侧的值)
<properties resource="jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springdb
jdbc.username=root
jdbc.password=123456
mapper标签
使用mapper指定其他mapper文件的位置,mapper标签使用的格式有两个常用的方式。
<mappers>
<!--
第一种方式:resources="mapper文件的路径"
优点:文件清晰,加载的文件是明确的,
文件的位置比较灵活。
缺点:文件比较多时,代码量会比较大。
-->
<mapper resource="com/hao/dao/StudentDao.xml"/>
<!--
第二种方式:使用<package>
name:包名,mapper文件所在的包名。
特点:把这个包中的所有的mapper文件,一次加载
使用要求:
1.mapper文件和dao接口在同一目录
2.mapper文件和dao接口名称完全一样
-->
<package name="com.hao.dao"/>
</mappers>
PageHelper
PageHelper做数据分页,在select语句后面加入分页的SQL内容,如果使用的是mysql数据库,他就是在select * from student 后面加入limit语句。
使用步骤:
1)加入PageHelper依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
2)在mybatis主配置文件,加入plugin声明
在<environments>之前加入
<plugins>
<plugin interceptor="com.github.pagehelper.PageIntercapter"/>
</plugins>
3)在select语句之前,调用PageHelper.startPage(页码,每页大小)
PageHelper.startPage(1,3);