1 基于代理Dao实现CRUD操作2 模糊查询的两种方式2.1 #{}和${}的区别3 Mybatis和JDBC编程的比较4 参数传递实体类的包装类(parameterType)5 ResultType6 ResultMap7 SqlMapConfig.xml配置文件7.1 properties7.2 typeAliases(类型别名)7.3 mappers(映射器,重要)8 Mybatis的自动提交事务的设置9 动态SQL9.1 selectKey10 多表查询10.1 一对一查询10.2 多对一查询10.3 多对多查询 1 基于代理Dao实现CRUD操作 使用要求: 1、持久层接口和持久层接口的映射配置必须在相同的包下 2、持久层映射配置中 mapper 标签的 namespace 属性取值必须是持久层接口的全限定类名 3、SQL 语句的配置标签,,,的 id 属性必须和持久层接口的 方法名相同。 获取Dao代理对象 @Before //用于再测试方法执行之前执行 public void Init() throws IOException { //1. 读取配置文件,生成字节输入流 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2. 获取SqlSessionFactory SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in); //3. 获取SqlSession对象 sqlsession = factory.openSession(); //4. 获取dao的代理对象 userDao = sqlsession.getMapper(UserDao.class); } 在映射的配置文件(UserDao.xml)中配置 <!-- 根据 id 查询 --> <select id="findById" resultType="com.itheima.domain.User" parameterType="int"> select * from user where id = #{uid} </select> <!-- resultType 属性:用于指定结果集的类型。 parameterType 属性:用于指定传入参数的类型。取值可以是基本类型,引用类型(String)、实体类类型(POJO)类,同时也可以是实体类的包装类 id: 要和全限定类的对应的方法名一致 #{}:表示占位符 --> <!-- 保存用户--> <insert id="saveUser" parameterType="com.itheima.domain.User"> insert into user(username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> 细节: parameterType 属性: 代表参数的类型,因为我们要传入的是一个类的对象,所以类型就写类的全名称。 sql 语句中使用#{}字符: 它代表占位符,相当于原来 jdbc 部分所学的?,都是用于执行语句时替换实际的数据。 具体的数据是由#{}里面的内容决定的。 #{}中内容的写法: 由于我们保存方法的参数是 一个 User 对象,此处要写 User 对象中的属性名称。 它用的是 ognl 表达式。 ognl 表达式: 它是 apache 提供的一种表达式语言,全称是: Object Graphic Navigation Language 对象图导航语言 它是按照一定的语法格式来获取数据的。 语法格式就是使用 #{对象.对象}的方式 #{user.username}它会先去找 user 对象,然后在 user 对象中找到 username 属性,并调用getUsername()方法把值取出来。但是我们在 parameterType 属性上指定了实体类名称,所以可以省略 user.而直接写username. 2 模糊查询的两种方式 第一种配置方式 XML配置: <!-- 根据名称模糊查询 --> <select id="findByName" resultType="com.itheima.domain.User" parameterType="String"> select * from user where username like #{username} </select> 业务代码: /* 我们在配置文件中没有加入%来作为模糊查询的条件,所以在传入字符串实参时,就需要给定模糊查询的标识%。配置文件中的#{username}也只是一个占位符,所以 SQL 语句显示为“?”。 */ List<User> users = userDao.findByName("%王%"); for(User user : users){ System.out.println(user); } 第二中配置方式 XML: 第一步:修改 SQL 语句的配置,配置如下: <!-- 根据名称模糊查询 --> <select id="findByName" parameterType="string" resultType="com.itheima.domain.User"> select * from user where username like '%${value}%' </select> 我们在上面将原来的#{}占位符,改成了${value}。注意如果用模糊查询的这种写法,那么${value}的写 法就是固定的,不能写成其它名字,参数只能叫value 业务代码: /* 可以发现,我们在程序代码中就不需要加入模糊查询的匹配符%了,这两种方式的实现效果是一样的,但执行的语句是不一样的。 */ List<User> users = userDao.findByName("王"); for(User user : users){ System.out.println(user); } 2.1 #{}和${}的区别 **#{}** 表示一个占位符号: 通过 #{} 可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{} 可以有效防止 sql 注入 。 #{} 可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值, #{} 括号中参数名可以是 value 或其它名称。**${}** 表示拼接 sql 串 :通过 ${} 可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${} 可以接收简 单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, ${} 括号中参数名只能是 value。 为什么只能是 ${value} ? 源码分析: 3 Mybatis和JDBC编程的比较 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。 解决: 在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。 Sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。 解决: 将 Sql 语句配置在 XXXXmapper.xml 文件中与 java 代码分离。 向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数对应。 解决: Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的 类型。 对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对 象解析比较方便。 解决: Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的 类型。 4 参数传递实体类的包装类(parameterType)POJO类中包含pojo对象: public class QueryVo implements Serializable { private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } } 映射文件XML: <!-- 根据用户名称模糊查询,参数变成一个 QueryVo 对象了 --> <select id="findByVo" resultType="com.itheima.domain.User" parameterType="com.itheima.domain.QueryVo"> select * from user where username like #{user.username}; </select> 测试文件: @Test public void testFindByQueryVo() { QueryVo vo = new QueryVo(); User user = new User(); user.setUserName("%王%"); vo.setUser(user); List<User> users = userDao.findByVo(vo); for(User u : users) { System.out.println(u); } } 5 ResultType resultType 属性可以指定结果集的类型,它支持基本类型和实体类类型。 我们在前面的 CRUD 案例中已经对此属性进行过应用了。 需要注意的是,它和 parameterType 一样,如果注册过类型别名的,可以直接使用别名。没有注册过的必须 使用全限定类名。 例如:我们的实体类此时必须是全限定类名. 同时,当是实体类名称时,还有一个要求,实体类中的属性名称必须和查询语句中的列名保持一致,否则无法 实现封装。 6 ResultMap resultMap 标签可以建立查询的列名和实体类的属性名称不一致时建立对应关系。从而实现封装。 在 select 标签中使用 resultMap 属性指定引用即可。同时 resultMap 可以实现将查询结果映射为复杂类 型的 pojo,比如在查询结果映射对象中包括 pojo 和 list 实现一对一查询和一对多查询。 XML配置 <!-- 建立 User 实体和数据库表的对应关系 type 属性:指定实体类的全限定类名 id 属性:给定一个唯一标识,是给查询 select 标签引用用的。 --> <resultMap type="com.itheima.domain.User" id="userMap"> <id column="id" property="userId"/> <result column="username" property="userName"/> <result column="sex" property="userSex"/> <result column="address" property="userAddress"/> <result column="birthday" property="userBirthday"/> </resultMap> id 标签:用于指定主键字段 result 标签:用于指定非主键字段 column 属性:用于指定数据库列名 property 属性:用于指定实体类属性名称 映射配置: <!-- 配置查询所有操作 --> <select id="findAll" resultMap="userMap"> select * from user </select> 两者在同一个配置文件下 7 SqlMapConfig.xml配置文件 包含的内容 -properties(属性) --property -settings(全局配置参数) --setting -typeAliases(类型别名) --typeAliase --package -typeHandlers(类型处理器) -objectFactory(对象工厂) -plugins(插件) -environments(环境集合属性对象) --environment(环境子属性对象) ---transactionManager(事务管理) ---dataSource(数据源) -mappers(映射器) --mapper --package 7.1 properties 在使用 properties 标签配置时,我们可以采用两种方式指定属性配置。 第一种 <properties> <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/> <property name="jdbc.url" value="jdbc:mysql://localhost:3306/eesy"/> <property name="jdbc.username" value="root"/> <property name="jdbc.password" value="1234"/> </properties> 第二种 :在classpath下定义properties文件 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/eesy jdbc.username=root jdbc.password=1234 所对应的在XML文件下的配置: <!-- 配置连接数据库的信息 resource 属性:用于指定 properties 配置文件的位置,要求配置文件必须在类路径下 resource="jdbcConfig.properties" url 属性: URL: Uniform Resource Locator 统一资源定位符 http://localhost:8080/mystroe/CategoryServlet URL 协议 主机 端口 URI URI:Uniform Resource Identifier 统一资源标识符 /mystroe/CategoryServlet 它是可以在 web 应用中唯一定位一个资源的路径 --> <properties url=jdbcConfig.properties对应的绝对路径></properties> <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> 7.2 typeAliases(类型别名) 自定义别名 在 SqlMapConfig.xml 中配置: <typeAliases> <!-- 单个别名定义 --> <typeAlias alias="user" type="com.itheima.domain.User"/> <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) --> <package name="com.itheima.domain"/> <package name="其它包"/> </typeAliases> 7.3 mappers(映射器,重要) , 用于配置文件配置 使用相对于类路径的资源 <mapper resource="com/itheima/dao/IUserDao.xml" /> , 用于注解配置 使用mapper接口类路径 <mapper class="com.itheima.dao.UserDao"/> 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,使用class属性指定被注解的dao全限定类名 注册指定包下的所有mapper接口,是不是和@MapperScan有一样的作用 如:<package name="cn.itcast.mybatis.mapper"/> 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。 8 Mybatis的自动提交事务的设置自动提交事务的方法: setAutoCommit( ) 事务提交的方式—->手动提交 @Test public void testSaveUser() throws Exception { User user = new User(); user.setUsername("mybatis user09"); //6.执行操作 int res = userDao.saveUser(user); System.out.println(res); System.out.println(user.getId()); } @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 的代理对象 userDao = session.getMapper(IUserDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ //7.提交事务!!!!!!!!!!!!! session.commit(); //8.释放资源 session.close(); in.close(); } 自动提交事务 @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(true); // 这里赋值为true,说明开启自动提交,所以在 // After之后,就不用再手动的commit代码了 //5.创建 Dao 的代理对象 userDao = session.getMapper(IUserDao.class); } @After//在测试方法执行完成之后执行 public void destroy() throws Exception{ //7.释放资源 session.close(); in.close(); } 对应的DefaultSqlSessionFactory源码 : 9 动态SQL当sql语句的条件有多个的时候,可以在对应的Dao配置文件下使用<if>、<where>、<forEach>或者使用集合封装的方式(parameterType的值为要封装的集合) 示例: <!--根据queryvo中的Id集合实现查询用户列表--> <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="id" separator=","> #{id} </foreach> </if> </where> </select> <!--根据条件查询--> <select id="findUserByCondition" resultMap="userMap" parameterType="user"> select * from user <where> <if test="username != null"> and username = #{username} </if> <if test="sex != null"> and sex = #{sex} </if> </where> </select> 9.1 selectKeyselectKey:配置插入操作后获取插入数据的id <!--保存用户--> <insert id="saveUser" parameterType="com.itheima.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> 10 多表查询 10.1 一对一查询 需求:查询所有账户信息,关联查询下单用户信息。 注意:因为一个账户信息只能供某个用户使用,所以从查询账户信息出发关联查询用户信息为一对一查询。如 果从用户信息出发查询用户下的账户信息则为一对多查询,因为一个用户可以有多个账户。 Sql语句如下: SELECT account.*, user.username, user.address FROM account, user WHERE account.uid = user.id 方式一:新建类 一般定义专门的po类作为 ``输出类型(resultType) ,其中定义了 sql 查询结果集所有的字段。此方法较为简单,企业中使用普遍 public class AccountUser extends Account { private String username; private String address; ...... } 方式二:修改Account类 在Account 类中加入 User 类的对象作为 Account 类的一个属性。 /** * * <p>Title: Account</p> * <p>Description: 账户的实体类</p> * <p>Company: http://www.itheima.com/ </p> */ public class Account implements Serializable { private Integer id; private Integer uid; private Double money; // 添加User类,作为属性的一部分 private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } ...... } 重新定义AccountDao.xml文件 ,使用 association <?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"> <!-- 建立对应关系 --> <resultMap type="account" id="accountMap"> <id column="aid" property="id"/> <result column="uid" property="uid"/> <result column="money" property="money"/> <!-- 它是用于指定从表方的引用实体属性的 --> <association property="user" javaType="user"> <id column="id" property="id"/> <result column="username" property="username"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <result column="address" property="address"/> </association> </resultMap> <select id="findAll" resultMap="accountMap"> select u.*,a.id as aid,a.uid,a.money from account a,user u where a.uid =u.id; </select> </mapper> 10.2 多对一查询 需求: 查询所有用户信息及用户关联的账户信息。 分析: 用户信息和他的账户信息为一对多关系,即一名用户拥有多个账户,并且查询过程中如果用户没有账户信息,此时也要将用户信息 查询出来,我们想到了左外连接查询比较合适。 sql语句如下: SELECT u.*, acc.id id, acc.uid, acc.money FROM user u LEFT JOIN account acc ON u.id = acc.uid User类加入List public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Account> accounts; public List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } ......(getter && setter) } 用户持久层Dao的配置 <mapper namespace="com.itheima.dao.IUserDao"> <resultMap type="user" id="userMap"> <id column="id" property="id"></id> <result column="username" property="username"/> <result column="address" property="address"/> <result column="sex" property="sex"/> <result column="birthday" property="birthday"/> <!-- collection 是用于建立一对多中集合属性的对应关系 ofType 用于指定集合元素的数据类型 --> <collection property="accounts" ofType="account"> <id column="aid" property="id"/> <result column="uid" property="uid"/> <result column="money" property="money"/> </collection> </resultMap> <!-- 配置查询所有操作 --> <select id="findAll" resultMap="userMap"> select u.*,a.id as aid ,a.uid,a.money from user u left outer join account a on u.id =a.uid </select> </mapper> collection:部分定义了用户关联的账户信息。表示关联查询结果集 ,用于建立一对多中集合属性的对应关系property=”accList”: 关联查询的结果集存储在 User 对象的上哪个属性ofType=”account”: 指定关联查询的结果集中的对象类型即List中的对象类型。此处可以使用别名,也可以使用全限定名 10.3 多对多查询 需求: 实现查询所有对象并且加载它所分配的用户信息 分析: 查询角色我们需要用到Role表,但角色分配的用户的信息我们并不能直接找到用户信息,而是要通过中 间表(USER_ROLE 表)才能关联到用户信息 sql语句 SELECT r.*,u.id uid, u.username username, u.birthday birthday, u.sex sex, u.address address FROM ROLE r INNER JOIN USER_ROLE ur ON (r.id = ur.rid) INNER JOIN USER u ON (ur.uid = u.id); 角色实体类 public class Role implements Serializable { private Integer roleId; private String roleName; private String roleDesc; //多对多的关系映射:一个角色可以赋予多个用户 private List<User> users; public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } ...... } 映射文件 <mapper namespace="com.itheima.dao.IRoleDao"> <!--定义 role 表的 ResultMap--> <resultMap id="roleMap" type="role"> <id property="roleId" column="rid"></id> <result property="roleName" column="role_name"></result> <result property="roleDesc" column="role_desc"></result> <collection property="users" ofType="user"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="address" property="address"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> </collection> </resultMap> <!--查询所有--> <select id="findAll" resultMap="roleMap"> select u.*,r.id as rid,r.role_name,r.role_desc from role r left outer join user_role ur on r.id = ur.rid left outer join user u on u.id = ur.uid </select> </mapper>