什么是框架
框架(FrameWork)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。简而言之,框架就是某种应用的半成品,就是一组组件,供你选用来完成自己的系统,也可以将框架理解为使用别人搭建好的舞台,但是需要自己上台表演。
使用框架的好处
框架封装了很多的细节,使开发者可以使用极简的方式实现功能,很大程度上提升了开发的效率
JDBC使用演示:
public static void main(String[] args) {
Connection connection = null;
ResultSet resultSet = null;
Statement statement = null;
try {
Class.forName("com.mysql.jdbc.Driver");
//获取连接
String URL = "jdbc:mysql://localhost:3306/mybatis_test";
String user = "root";
String psword = "123456";
connection = DriverManager.getConnection(URL, user, psword);
//获取statement对象
statement = connection.createStatement();
//执行查询操作
String sql = "select * from student";
resultSet = statement.executeQuery(sql);
//处理结果集
while (resultSet.next()) {
String id = resultSet.getString("SID");
String name = resultSet.getString("Sname");
String sex = resultSet.getString("Ssex");
String age = resultSet.getString("Sage");
System.out.println(id + "::" + name + "::" + sex + "::" + age);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
JDBC编程存在的问题
1.数据库连接频繁的被创建和关闭,无法达到资源的复用
解决办法:可以采用连接池来达到连接的复用,也就是connection的复用
2.SQL语句和Java代码存在耦合,需求改变后,就需要改变源代码,不利于后期维护
解决办法:将SQL语句放在配置文件中,当需求改变后,我们仅需修改配置文件
3.结果集中需要对数据的类型进行判断,降低了开发效率
解决办法:把从数据库中查询到的结果直接映射到Java对象
MyBatis介绍
在MyBatis官网的介绍中有这样一段话:
MyBatis是一流的持久性框架,支持自定义SQL,存储过程和高级映射。MyBatis消除了几乎所有的JDBC代码以及参数的手动设置和结果检索。MyBatis可以使用简单的XML或注释进行配置,并将图元,映射接口和Java POJO(普通的旧Java对象)映射到数据库记录。
MyBaits原理
1.全局配置文件:配置数据源、事务、引入映射文件
2.映射文件:mapper1.xml mapper2.xml 、结果集的封装
3.SqlSessionFactory:会话工厂,用来打开会话(SqlSession)
4.SqlSession:会话,可以进行增删改查操作
5.Executor:执行器
6.MappedStatement:将输入的解析映射到数据库,把数据库输出的映射到结果集
Mybatis的使用
数据库中的表:
±----±----------±-----±-----+
| SID | Sname | Ssex | Sage |
±----±----------±-----±-----+
| 1 | sds | boy | 50 |
| 2 | rym | boy | 21 |
| 3 | leixingyi | boy | 20 |
| 4 | lsj | boy | 20 |
| 5 | wrx | boy | 20 |
±----±----------±-----±-----+
具体案例:通过Sname来查询学生的信息
引入依赖
<!--mysql操作-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!--mybatis配置-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.1</version>
</dependency>
全局配置文件(mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--根标签-->
<configuration>
<!--数据源配置-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis_test"/>
<property name="username" value="root}"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
</configuration>
创建POJO类
public class Student {
private Integer SID;
private String Sname;
private String Ssex;
private Integer Sgae;
public Integer getSID() {
return SID;
}
public void setSID(Integer SID) {
this.SID = SID;
}
public String getSname() {
return Sname;
}
public void setSname(String sname) {
Sname = sname;
}
public String getSsex() {
return Ssex;
}
public void setSsex(String ssex) {
Ssex = ssex;
}
public Integer getSgae() {
return Sgae;
}
public void setSgae(Integer sgae) {
Sgae = sgae;
}
@Override
public String toString() {
return "Student{" +
"SID=" + SID +
", Sname='" + Sname + '\'' +
", Ssex='" + Ssex + '\'' +
", Sgae=" + Sgae +
'}';
}
}
Mapper接口文件
/**
* 通过Sname查询数据实体
* @param name
* @return
*/
public Student selectStudentByName(String name);
配置Mapper.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">
修改全局配置文件(mybatis-config.xml)
此时需要将Student Mapper.xml文件配置到全局配置文件上
<!--配置映射-->
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
进行查询操作
public static void main(String[] args) throws IOException {
//mybatis配置文件
String resource = "mybatis-config.xml";
//读取配置文件
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
//创建会话工厂,传输mybatis配置文件信息
//这里使用了构建者模式(让使用者直接调用方法就可以拿到对象,屏蔽了那些繁琐的操作)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//通过会话工厂得到SqlSession
//这里使用了工厂模式(解耦:降低了类之间的依赖关系)
SqlSession sqlSession = sqlSessionFactory.openSession();
//这里使用了代理模式(在不修改源码的基础上,实现了对原有方法的增强)
UserDao userDao = sqlSession.getMapper(UserDao.class);
//调用userDao实例下的查询方法
User user = userDao.selectStudentByName("rym");
System.out.println(user);
resourceAsStream.close();
sqlsession.close();
}
通过以上代码可知,我们在使用查询操作之前,需要读取配置文件、创建SqlSessionFactory、SqlSession等一系列操作,为了简化我们的操作,可以创建一个测试类,并且将InputStream对象、SqlSession对象、接口对象设置为测试类的属性,然后使用注解@Before和@After来完成完成与数据库交互操作之前的准备操作与善后工作
(使用了@Before注解的方法会在使用了@Test注解的方法之前执行,使用了@After注解的方法会在使用了@Test注解的方法之后执行)
代码如下:
private InputStream fin;
private SqlSession sqlSession;
private IUserDao userDao;
/**
* 初始化资源
*
* @throws IOException
*/
@Before //用于在测试方法执行之前执行
public void init() throws IOException{
//1.读取配置文件,生成字节输入流
fin = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.获取SqlSessionFactoryBuilder对象(作为构建者(包工队))
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.让构建者来创建SqlSessionFactory(会话工厂)
SqlSessionFactory factory = builder.build(fin);
//4.使用SqlSessionFactory创建SqlSession(会话)
sqlSession = factory.openSession();
//5.利用反射机制来创建IUserDao接口的代理对象
userDao = sqlSession.getMapper(IUserDao.class);
}
/**
* 释放资源
*/
@After //用于在测试方法执行之后执行
public void destroy() throws IOException {
fin.close();
sqlSession.close();
}
插入一个新用户
//对应的xml配置文件
<!--保存用户-->
<insert id="insertUser" parameterType="com.rym.domain.User" >
<!--配置插入操作后,获取插入数据的id-->
<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>
/**
* 测试根据
*/
@Test
public void testInsertUser(){
//创建一个新的User对象
User user = new User();
user.setUserName("wrx");
user.setUserAddress("China");
user.setUserBirthday(new Date(1999 - 1 - 1));
user.setUserSex("m");
System.out.println("保存操作之前:" + user);
//执行插入操作
userDao.insertUser(user);
System.out.println("保存操作之后:" + user);
System.out.println(user);
//提交事务
sqlSession.commit();
}
根据id删除用户信息
//对应的xml配置文件
<!--根据id删除用户-->
<delete id="deleteUserById" parameterType="java.lang.Integer">
delete from user where id = #{uid}
</delete>
/**
* 测试根据id删除用户
*/
@Test
public void testDeleteUserById(){
Integer id = 50;
userDao.deleteUserById(id);
sqlSession.commit();
}
更新用户信息
//对应的xml配置文件
<!--更新用户-->
<update id="updateUser" parameterType="com.rym.domain.User">
update user set
username = #{username},address = #{address},sex = #{sex},birthday=#{birthday}
where id = #{id}
</update>
/**
* 测试更新操作
*/
@Test
public void testUpdateUser() {
User user = new User();
user.setUseId(41);
user.setUserName("rookie");
user.setUserSex("m");
user.setUserBirthday(new Date(1998 - 1 - 1));
user.setUserAddress("korea");
userDao.updateUser(user);
sqlSession.commit();
}
配置结果集映射
当我们使用POJO类中的属性名和MySQL数据库中表的字段名不一致时,我们从数据库查询到的结果就无法很好的封装到POJO类中,具体表现为:在打印的时候,很多属性的值都为null,而不是我们所要的结果。为了解决这一问题,我们可以在每个接口对应xml文件中配置resultMap,其代码如下:
<!--配置查询结果的列名和实体类的属性名的对应关系-->
<resultMap id="userMap" type="com.rym.domain.User">
<!--主键字段的对应-->
<id property="userId" column="id"/>
<!--非主键字段的对应-->
<result property="userName" column="username"/>
<result property="userSex" column="sex"/>
<result property="userAddress" column="address"/>
<result property="userBirthday" column="birthday"/>
</resultMap>
Mybatis中的连接池
连接池:
连接池其实就是用于存储连接的一个容器,而这个容器是用一个线程安全的集合对象来表示的,这是为了避免两个线程拿到同一个连接。并且该集合还需要实现队列的特性(先进先出)
mybatis中的连接池:
mybatis连接池提供了3中方式的配置(POOLED、UNPOOLED、JNDI)
配置的位置:主配置文件SqlMapConfig.xml中的dataSource标签,其中,type属性就是表示采用何种连接池方式。
type属性的取值:
POOLED:
采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对该规范的实现
当使用POOLED时,进行查询操作所打印的日志会出现这样的语句:
使用POOLED时,所用的类是PooledDataSource,与数据库建立连接的过程如下:
加锁是为了保证线程安全,防止多个线程拿到同一个连接
当连接池有空闲连接时,直接取出来
当连接池中无空闲连接,并且活动的连接小于其最大值时,可以创建一个连接
当以上情况都不满足时,就从活动池中取出存货时间最久的一个连接
UNPOOLED:
采用传统的获取连接的思想,尽管其也实现了javax.sql.DataSource接口,但是却没有使用“池”的思想
在使用UNPOOLED时,执行查询操作所打印的日志则为这样:
使用UNPOOLED时,所用的类是UnpooledDataSource,与数据库建立连接的过程:
JNDI:
采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到的DataSource是不一样的。注意:如果不是web或者maven的war工程,是不能使用的。在tomcat服务器中使用的是dbcp连接池。
Mybatis中的事务控制
在Mybatis中,是通过SqlSession对象的commit方法和rollback方法来实现事务的提交和回滚
什么是事务
事务的四大特性
不考虑隔离性会产生的3个问题
四种隔离级别
基于XML配置的动态SQL语句使用
<if>标签的使用
我们根据实体类的不同取值,使用不同的SQL语句进行查询。例如:在id不为空时,使用id作为条件进行查询,在username不为空时,还需要将username作为条件进行查询。其标签写法如下:
<!--根据条件查询-->
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user where 1=1
<if test="userId != null">
and id = #{userId}
</if>
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and sex = #{userSex}
</if>
</select>
<where>标签的使用:
<select id="findUserByCondition" resultMap="userMap" parameterType="user">
select * from user
<where>
<if test="userId != null">
and id = #{userId}
</if>
<if test="userName != null">
and username = #{userName}
</if>
<if test="userSex != null">
and sex = #{userSex}
</if>
</where>
</select>
<foreach>标签的使用
在从数据库查询数据的过程中,我们难免会遇到一些需要使用多个相同的属性作为条件进行查询的情况,例如:查询id为12、32、55的用户,此时,我们使用的SQL语句为:select * from user where id in (12,32,55); 为了完成这样的查询效果,我们就需要用到foreach标签,以下为该标签的具体写法:
<!--根据QueryVo中提供的id集合,查询用户列表
其中QueryVo是一个专门用来查询的类,ids是类中的一个属性:List<Integer> ids-->
<select id="findUserInIds" resultMap="userMap" parameterType="com.rym.domain.QueryVo">
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>
Mybatis中的多表操作
一对一的查询:
场景:现在有一张user表与一张account表,user表中的id为主键,account表中的uid为与user表相关联的外键,现在要执行查询语句:
select a.id,a.uid,a.money,u.* from account as a,user as u
where u.id = a.uid
user表与account表具体属性如下:
user表:
account表:
在mybatis中,要使用一对一的多表查询,我们需要提前为两张表分别创建两个对应的类,也就是User类与Account类。将User表当作主表,Account表当作从表,则需要在Account类中额外设置一个User类型的属性(从表实体中应该包含一个主表实体的引用),这是因为我们在查询完成后,结果会封装在Account类中,而返回的结果中会包含User表中的属性,所以需要一个在Account类中添加User属性,用于接收查询到的结果。
其对应的xml配置文件如下:
<!--定义封装account和user的resultMap-->
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"/>
<result property="money" column="money"/>
<!--建立一对一的关系映射,配置封装user的内容-->
<association property="user" column="uid" javaType="user">//此处的javaTpye中要写所使用类的全限定名,而我起了别名,所以不写
<!--property对应类中的属性,column对应数据库中的字段-->
<id property="id" column="uid"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
</association>
</resultMap>
<select id="findAllAccount1" resultMap="accountUserMap">
select a.id,a.uid,a.money,u.* from account as a,user as u
where u.id = a.uid
</select>
一对多的查询:
场景:一个用户可以拥有多个账户,而每个账户只能被一个用户所拥有。可以创建有一张user表与一张account表,user表中的id为主键,account表中的uid为与user表相关联的外键,我们需要将user表作为主表,将user表中的每一行都打印出来,无论其有没有关于account的信息
要执行的查询语句为:
select u.*,a.id,a.uid,a.money from user as u left outer join account as a
on u.id = a.uid
执行查询语句后,将需要将结果封装到List<User>中,所以需要在User类中添加额外的字段List<Account>,因为返回的结果中可能不光包含user表中的信息,还会包含account表中的信息,所以需要额外的属性来接收它
其对应的xml配置文件如下:
<!--配置查询结果的列名和实体类的属性名的对应关系-->
<!--定义user的resultMap-->
<resultMap id="userAccountMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<result property="sex" column="sex"/>
<result property="birthday" column="birthday"/>
<!--配置user对象中accounts集合的映射-->
<collection property="accounts" ofType="account">
<id column="aid" property="id" />
<result column="uid" property="uid" />
<result column="money" property="money" />
</collection>
</resultMap>
<select id="findAllUserWithAccount" resultMap="userAccountMap">
select u.*,a.id,a.uid,a.money from user as u left outer join account as a
on u.id = a.uid
</select>
多对多查询
场景:一个用户可以有多个角色,例如,一个人在学校的时候,他扮演的是学生的角色,而回到家后,扮演的就是子女的角色了;一个角色可以赋予多个用户,例如,一个教师里的多个人都有学生这一个角色。所以我们可以 ① 建立两张表,一个张用户表,一张角色表;另外,为了使用户表和角色表之间具有多对多的关系,我们还需要建立一张中间表,在中间表中包含用户表和角色表的主键,作为自己的外键;② 建立两个实体类, 也就是用户类和角色类;为了让用户实体和角色实体能够体现出多对多的关系,需要让用户类和角色类各自包含对方的一个集合引用;③ 建立对应的配置文件;④ 实现的功能:在查询用户信息时,同时得到该用户所对应的角色信息,在查询角色时,同时得到该角色的所属用户信息
user表:
role表:
中间表:
当user表为主表时,查询的结果中必须包含所有的user信息,并且包含对应的角色信息(如果存在),其使用的select语句为:
select u.*,r.id,r.role_name,r.role_desc from user as u
left join user_role as ur on u.id = ur.uid
left join role as r on ur.rid = r.id
当使用role表为主表,查询的结果中必须包含所有的角色信息,并且包含对应的user信息(如果存在),其使用的select语句为:
select u.*,r.id,r.role_name,r.role_desc from role as r
left outer join user_role as ur on r.id = ur.rid
left outer join user as u on u.id = ur.uid