Dao模式(Data Access Objects)[数据存取对象]
- 概念:指位于业务逻辑和持久化数据之间实现对持久化数据的访问(封装数据库操作)。
- 详细回顾:https://www.runoob.com/note/27029
基于代理Dao实现CRUD操作(重点)
配置查询结果的列名和实体类的属性名的对应关系
<resultMap id="userMap" type="com.example.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>
查询操作
- 查询所有用户:
List<User> findAll();
<select id="findAll" resultMap="userMap">
<!--select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;-->
select * from ;
</select>
- 根据id查询用户:
User findById(Integer userId);
<select id="findById" parameterType="INT" resultMap="userMap">
select * from user where id = #{uid};
</select>
- 根据名称模糊查询:
List<User> findByName(String username);
<select id="findById" parameterType="INT" resultMap="userMap">
select * from user where id = #{uid};
</select>
- 查询总用户数:
int findTotal();
<select id="findTotal" resultMap="userMap">
select count(id) from user;
</select>
- 根据queryVo的条件查询用户:
List<User> findUserByVo(Query vo);
<select id="findUserByVo" parameterType="com.example.domain.QueryVo">
select * from user where username like #{user.username}
</select>
保存操作
- 保存用户:
void saveUser(User user);
<insert id="saveUser" parameterType="user">
<!-- 配置插入操作,获取插入数据的id -->
<!-- keyProperty(id的属性名称对应实体类)keyColumn(id的列名对应表)resultType(结果值类型)order(什么时候执行获取id的操作) -->
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into user(username,address,sex,birthday)values(#{userName},#{userAddress},#{userSex},#{userBirthday});
</insert>
更新操作
- 更新用户:
void updateUser(User user);
<update id="updateUser" parameterType="com.example.domain.User">
update user set username=#{userName},address=#{userAddress},sex=#{userSex},birthday=#{userBirthday} where id=#{userId};
</update>
删除操作
- 根据id删除用户:
User findById(Integer userId);
<delete id="deleteUser" parameterType="java.lang.Integer">
delete from user where id = #{uid}
</delete>
Mybatis基于传统dao方式的使用(了解)
持久层Dao接口:
public interface UserDao {
/**
* 查询所有用户
* @return
*/
List<User> findAll();
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
}
持久层Dao实现类:
import com.example.dao.UserDao;
import com.example.domain.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.List;
public class UserDaoImpl implements UserDao {
private SqlSessionFactory factory;
public UserDaoImpl(SqlSessionFactory factory) {
this.factory = factory;
}
@Override
public List<User> findAll() {
//1. 根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2. 调用SqlSession中的方法,实现查询列表
List<User> users = session.selectList("findAll22");//参数就是能获取配置信息的key
// UserDao userDao = session.getMapper(UserDao.class);
// List<User> users = userDao.findAll();
//3. 释放资源
session.close();
return users;
}
@Override
public User findById(Integer userId) {
//1. 根据factory获取SqlSession对象
SqlSession session = factory.openSession();
//2. 调用SqlSession中的方法,实现查询一个
User users = session.selectOne("findById");
//3. 释放资源
session.close();
return users;
}
}
持久层映射配置:
<?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 namespace="com.example.dao.UserDao">
<!-- 查询所有 -->
<select id="findAll" resultType="com.example.domain.User">
<!--select id as userId,username as userName,address as userAddress,sex as userSex,birthday as userBirthday from user;-->
select * from user;
</select>
<select id="findById" resultType="com.example.domain.User">
select
</select>
</mapper>
测试类:
import com.example.dao.UserDao;
import com.example.dao.impl.UserDaoImpl;
import com.example.domain.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
public class MybatisTest {
private InputStream is;
private SqlSession session;
private UserDao userDao;
@Before//用于测试方法执行之前执行
public void init() throws Exception {
System.out.println("1");
//1. 读取配置文件,生成字节输入流
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2. 获取SqlSessionFactory
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//3. 使用工厂对象,创建dao对象
userDao = new UserDaoImpl(factory);
}
@After//用于测试方法执行之后执行
public void destroy() throws Exception{
//6. 释放资源
is.close();
}
/**
*测试查询所有
*/
@Test
public void testFindAll() {
//5. 执行查询所有方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
}
mybatis主配置文件中的常用配置
- properties标签:配置数据库信息。
- typeAliases标签:解释Integer的写法
- mapper标签的子标签:package
<?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>
<properties resource="jddbcConfig.properties"></properties>
<typeAliases>
<typeAlias type="com.example.domain.User" alias="user"></typeAlias>
<package name="com.example.domain"></package>
</typeAliases>
<!--配置环境-->
<environments default="mysql">
<!--配置mysql的环境-->
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置 -->
<mappers>
<package name="com.example.dao"></package>
</mappers>
</configuration>
自定义mybatis流程图
扩展知识:{}与${}的区别
- #{}表示一个占位符号
通过#{}可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换,
{}可以有效防止 sql 注入。 #{}可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{}括号中可以是 value 或其它名称。
- ${}表示拼接 sql串
通过${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}可以接收简
mybatis中的连接池以及事务控制:
- 在mybatis的SqlMapConfig.xml配置文件中,通过
<dataSource type="pooled">
连接池:
- 连接池: 在实际开发中均使用连接池,主要目的是减少获取连接所消耗的时间。
mybatis中的连接池:
mybatis连接池提供的三种配置方法:
- 配置的位置:主配置文件SqlMapConfig.xml中的dataSource标签。
- type属性的取值:
- POOLED 采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现
- UNPOOLED 采用传统的获取连接的方式,实现了Javax.sql.DataSource接口,但并没有使用池的思想。
- JNDI 采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource不同。
注意:
如果不是web或者maven的war工程,是不能使用的(tomcat服务器,采用连接池就是dbcp连接池。)
Mybatis连接池及事务控制
连接池
- 连接池概念:创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。
- 连接池的核心思想:连接复用,通过建立一个数据库连接池以及一套连接使用、分配、管理策略,使得该连接池中的连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。(减少获取连接所消耗的时间)
Mybatis的连接池
- 数据源的作用:更好的管理数据库连接,即为连接池技术。
type属性的取值
type属性(采用哪种连接池方式) | 注解 |
---|---|
POOLED | 使用连接池的数据源,直接从连接池获取数据源使用(主要) |
UNPOOLED | 不使用连接池的数据源,创建一个新的数据源使用 |
JNDI | 使用 JNDI 实现的数据源(不同的服务器得到的数据源不同) |
注意:当所创建的为web或maven的war工程时才能使用。
-
POOLED与UNPOOLED区别:
-
连接池配置位置:主配置文件SqlMapConfig.xml的dataSource标签中。
<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>
Mybatis中的事务
- 事务(Transaction):访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
- 事务的四大特性(ACID):原子性、一致性、隔离性、持久性
特性 | 注解 |
---|---|
原子性(atomicity) | 事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。 |
一致性(consistency) | 事务必须是使数据库从一个一致性状态变到另一个一致性状态。 |
隔离性(isolation) | 一个事务的执行不能被其他事务干扰。 |
持久性(durability) | 一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。 |
- 不考虑隔离性会产生的三大问题:
脏读:A事务中读取到了B事务中未提交的数据,造成数据错误。
不可重复读:A事务中读取到了B事务中已提交的数据,在特定情景下会产生影响,比如生成统一的数据报表。
虚读(幻读):A事务中读取到了B事务中已提交的新插入的数据。
- 解决办法:四种隔离级别:
- 注:事务通过SqlSession对象的commit方法和rollback方法实现事务的提交和回滚。
基于XML配置的动态SQL语句使用
<if>
:条件查询
<select id="findByUser" resultType="user" parameterType="user">
select * from user where 1=1
<if test="username!=null and username != '' ">
and username like #{username}
</if> <if test="address != null">
and address like #{address}
</if>
</select>
<where>
:简化上面 where 1=1 的条件拼装,我们可以采用标签来简化开发。
<select id="findByUser" resultType="user" parameterType="user"> <include refid="defaultSql"></include> <where> <if test="username!=null and username != '' ">
and username like #{username}
</if> <if test="address != null">
and address like #{address}
</if>
</where>
</select>
<foreach>
:用于遍历集合
<select id="findInIds" resultType="user" parameterType="queryvo">
<!-- select * from user where id in (1,2,3,4,5); --> <include refid="defaultSql"></include> <where> <if test="ids != null and ids.size() > 0"> <foreach collection="ids" open="id in ( " close=")" item="uid"
separator=",">
#{uid}
</foreach>
</if>
</where>
</select>
<sql>
:可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。
<!-- 抽取重复的语句代码片段 -->
<sql id="defaultSql">
select * from user
</sql>
<!-- 配置查询所有操作 --> <select id="findAll" resultType="user"> <include refid="defaultSql"></include>
</select>
Mybatis中的多表查询
- 表之间的关系:
- 多对一:多门课程对应一个专业
例如:
- 需求:查询所有账户信息,关联查询下单用户信息。
- 注意:因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。
/**
账户的实体类
*/
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
public Integer getId() {
return id; }
public void setId(Integer id) {
this.id = id; }
public Integer getUid() {
return uid; }
public void setUid(Integer uid) {
this.uid = uid; }
public Double getMoney() {
return money; }
public void setMoney(Double money) {
this.money = money; }
@Override
public String toString() {
return "Account [id=" + id + ", uid=" + uid + ", money=" + money + "]"; } }
public class AccountUser extends Account implements Serializable {
private String username;
private String address;
public String getUsername() {
return username; }
public void setUsername(String username) {
this.username = username; }
public String getAddress() {
return address; }
public void setAddress(String address) {
this.address = address; }
@Override
public String toString() {
return super.toString() + " AccountUser [username=" + username + ",
address=" + address + "]"; } }
/**
账户的持久层接口
*/
public interface IAccountDao {
/**
* 查询所有账户,同时获取账户的所属用户名称以及它的地址信息
* @return
*/
List<AccountUser> findAll();
}
/**
定义 AccountDao.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 namespace="com.itheima.dao.IAccountDao">
<!-- 配置查询所有操作--> <select id="findAll" resultType="accountuser">
select a.*,u.username,u.address from account a,user u where a.uid =u.id;
</select> </mapper>
/**
创建 AccountTest 测试类
*/
public class AccountTest {
private InputStream in ;
private SqlSessionFactory factory;
private SqlSession session;
private IAccountDao accountDao;
@Test
public void testFindAll() {
//6.执行操作
List<AccountUser> accountusers = accountDao.findAll();
for(AccountUser au : accountusers) {
System.out.println(au);
} }
@Before//在测试方法执行之前执行
public void init()throws Exception {
//1.读取配置文件
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建构建者对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.创建 SqlSession 工厂对象
factory = builder.build(in);
//4.创建 SqlSession 对象
session = factory.openSession();
//5.创建 Dao 的代理对象
accountDao = session.getMapper(IAccountDao.class);
}
@After//在测试方法执行完成之后执行
public void destroy() throws Exception{
session.commit();
//7.释放资源
session.close();
in.close();
} }
步骤:
1、建立两张表:用户表,账户表
让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
2、建立两个实体类:用户实体类和账户实体类
让用户和账户的实体类能体现出来一对多的关系
3、建立两个配置文件
用户的配置文件
账户的配置文件
4、实现配置:
当我们查询用户时,可以同时得到用户下所包含的账户信息
当我们查询账户时,可以同时得到账户的所属用户信息
- 一对一:学生与学号
- 多对多:学生与教师
- 一对多:订单与客户