赞
踩
SqlSessionFactoryBuilder接受SqlMapConfig.xml的文件流,构建出SqlSessionFactory对象
SqlSessionFactory读取SqlMapConfig.xml中连接数据库和mapper的映射信息,用来生产出真正操作数据库的SqlSession对象
SqlSession对象的两大作用:1) 生产接口代理对象;2) 定义通用增删改查方法
注意:两者除了获取数据库信息,还需要得到sql语句
作用1)
在SqlSessionImpl对象中(SqlSession的实现类对象)的getMapper方法分为两步骤:1) 用SqlSessionFactory读取数据库连接信息创建Connection对象;2) 通过jdk代理模式创建出代理对象作为getMapper方法的返回值,主要工作是在创建对象时第三个参数InnovationHandler接口的重写处理类里面得到sql语句,执行对象的CURD操作
作用2)
在SqlSessionImpl对象中提供selectList方法[其实在Mybatis框架中还有selectOne,insert等方法,同样分为两步走]:1) 用SqlSessionFactory读取的数据库连接信息创建出JDBC的Connection对象;2) 直接得到Sql语句,使用JDBC的Connection对象进行CRUD操作
封装结果集:无论使用分支一生成代理对象,还是直接使用分支二提供的通用CRUD方法,都要对返回的数据库结果进行封装,变成java对象返回给调用者,所以需要知道调用者所需的返回值类型(封装成什么类的对象)
总结: 无论是让Mybatis帮助创建代理对象还是直接使用Mybatis提供的CRUD方法,其本质都是得到JDBC的Connection对象,执行对应的sql语句,最终封装到结果集。只是注解和xml配置两种开发模式在传递sql语句和返回值类型的方式上有所差异:
/** * 用户的持久层接口 * */ public interface UserInterface { /** * 查询所有用户操作 * @return */ //@Select("select * from account") // 对于使用注解来配置UserDao List<User> findAll(); void saveUser(User user); // 新增用户方法 void updateUser(User user); // 更新用户方法 void deleteUser(Integer UserId); // 删除用户方法 }
<?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"> <!-- namespace名称空间,Dao接口的全类名,告诉mybatis这个配置文件是实现哪个接口的--> <mapper namespace="com.huzhen.domain.dao.UserInterface"> <!-- 配置查询表user所有记录的sql语句 并且加上resultType返回值类型,sql语句的查询结果集将会被封装到 User类中,最终返回一个List<User>列表--> <select id="findAll" resultType="com.huzhen.domain.User"> select * from account; </select> <insert id="saveUser" parameterType="com.huzhen.domain.User"> insert into account(username, birthday, sex, addres) values(#{username}, #{birthday}, #{sex}, #{addres}); </insert> <update id="updateUser" parameterType="com.huzhen.domain.User"> update account set username=#{username}, birthday=#{birthday}, sex=#{sex}, addres=#{addres} where id=#{id}; </update> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from account where id=#{Uid} </delete> </mapper>
注意:在JDBC中可以使用?来进行占位符,
insert into account values(?, ?, ?, ?)
而Mybatis中是通过增加**parameterType参数用来指定传递进来的参数是User类型下的参数,并且只有使用jdk自带的getter和Setter方法生成的获取参数和返回参数方法,才能使用#{}**来传递参数,否则需要指定get和Set的方法名而不是成员变量名。在删除的配置中,传入的parameterType中不再是自定义的封装类User而是根据什么来查询,这里根据id来查询,则传入进去的是Integer整型,如果是根据username或者其他的可以传入String类型。
parameterType和resultType的区别在于:如果该sql语句有输入,则指明parameterType,有输出则指明resultType
在test包下增加一个用于测试的类,由于安装的依赖是Junit版本是5.8.2,不能再使用注解==@BeforeAll和@AfterAll>==
而只能使用@BeforeAll和BeforeAll,并且这两个注解只能修饰静态方法,所以在成员变量处也需要设置static修饰符。之前对于读取配置文件,获取SqlSessionFactory来创建SqlSession对象,获取Dao接口的代理对象以及释放资源是单个写的,现在对于多个测试案例,可以将共用方法进行封装到init和close方法中,需要注意的是对于增加,删除和更新操作时,需要进行事务提交才能正常运行。
public class test { /** * 由于需要测试多个操作,而每个test中读取配置文件,获取SqlSessionFactory来创建对象SqlSession * 以及获取Dao的代理对象和释放资源都是共用的,所以可以封装在一个init方法中 */ private static InputStream is; private static SqlSession sqlSession; private static UserInterface userDao; @BeforeAll public static void init() throws IOException { is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); sqlSession = factory.openSession(); userDao = sqlSession.getMapper(UserInterface.class); } // AfterAll和BeforeAll注解修饰的必须是静态方法 @AfterAll public static void close() throws IOException{ sqlSession.commit(); // 对于更新,插入,删除操作必须有提交事物才能正常运行 sqlSession.close(); is.close(); } /** * 测试查询所有用户 */ @Test public void testFindAll(){ List<User> list = userDao.findAll(); for(User user : list){ System.out.println(user); } } /** * 测试新增用户 */ @Test public void testInsertUser(){ User user = new User(); user.setUsername("Bin"); user.setBirthday(new Date()); user.setSex("男"); user.setAddres("上海虹桥"); userDao.saveUser(user); // 必须需要提交事物才能添加成功,在close中执行 System.out.println("添加成功!"); } @Test public void testUpdate(){ User user = new User(); user.setId(19); user.setUsername("A Bin"); user.setBirthday(new Date()); user.setSex("男"); user.setAddres("上海滴水湖"); userDao.updateUser(user); } @Test public void testUser(){ userDao.deleteUser(19); } }
新增用户后,同时需要返回当前新增用户的id值,因为id是由数据库的**AUTO_INCREMENT**实现的。所以相当于在新增用户将自动增长auto_increment的值返回。
那么在XML中的insert标签中需要加入select last_insert_id()语句:
<insert id="saveUser" parameterType="com.huzhen.domain.User">
<!-- keyProperty表示返回的值名称对应实体类USer keyColumn对应表中的id order取值为after表示插入后的行为 -->
<selectKey keyProperty="id" keyColumn="id" order="AFTER" resultType="INT">
select last_insert_id();
</selectKey>
insert into account(username, birthday, sex, addres) values(#{username},
#{birthday}, #{sex}, #{addres});
</insert>
其中,**keyProperty表示返回值名称对应的是实体类User中的id,而KeyColumn对应的是表中的字段id,order表示下面的语句是在插入前执行还是在插入后执行,这里赋值AFTER**表示在查询之后返回id值
在test类中进行测试代码如下:
@Test
public void testInsertUser(){
User user = new User();
user.setUsername("Bin");
user.setBirthday(new Date());
user.setSex("男");
user.setAddres("上海虹桥");
System.out.println("插入用户前:" + user);
userDao.saveUser(user);
// 必须需要提交事物才能添加成功,在close中执行
System.out.println("添加成功!");
System.out.println("插入用户后:" + user);
}
输出结果为:
可以看出插入前后id的改变
和上面一样,首先在Dao接口中定义方法名,其次在XML配置文件中加入sql语句,最后在测试类中进行测试:
public interface UserInterface { /** * 定义sql语句的方法 * @return */ //@Select("select * from account") // 对于使用注解来配置UserDao List<User> findAll(); // 查询所有用户 void saveUser(User user); // 新增用户方法 void updateUser(User user); // 更新用户方法 void deleteUser(Integer UserId); // 删除用户方法 User findById(Integer userId); // 根据id查询用户信息 List<User> findByName(String userName); // 根据用户名模糊查询 int findTotalUser(); // 查询用户的总记录数 }
<select id="findById" parameterType="INT" resultType="com.huzhen.domain.User">
select * from account where id=#{Uid};
</select>
<select id="findByName" parameterType="String" resultType="com.huzhen.domain.User">
select * from account where username like #{name};
<!-- select * from account where username like '%${value}%' -->
</select>
<select id="findTotalUser" resultType="java.lang.Integer">
select count(id) from account;
</select>
@Test
public void testSelectById(){
User user = userDao.findById(2);
System.out.println(user);
}
查询结果为:
对于模糊查询,分为两种情况
select * from account where username like #{name};
在测试类中进行模糊查询的操作:List<User> list = userDao.findByName("%User%");
, 而在测试类中则无需在使用模糊查询了:
List list = userDao.findByName(“User”);`但是两者存在区别:对于i) 读取到的sql语句为:select * from account where username like '%王%'
;而对于ii) 读取到的sql语句是:select * from account where username like ?
配置文件的模糊查询得到的是Statement对象的字符串拼接SQl,而测试类的模糊查询得到的是PreparedStatement的参数占位符,因此不推荐在配置文件中使用模糊查询,易引起sql注入问题
查询结果为:
@Test
public void testTotalUserNumber(){
int number = userDao.findTotalUser();
System.out.println("总记录数:" + number);
}
查询结果为:
Mybatis使用**OGNL**表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称
OGNL:Object Graphic Navigation Languag 对象图导航语言,通过对象的取值方法来获取数据,在写法上将get给省略了,如:获取用户名称
在USer类中:user,getUsername();
OGNL表达式写法:user.username();
Mybatis中为什么可以直接写username
,而不使用 user.
?
因为在parameterType中已经提供了属性所属的类,如:com.huzhen.domain.User
,所以不再需要写对象名
开发中通过pojo传递查询条件,查询条件是综合的查询条件,不仅包括用户查询条件还包括其他的查询条件(如将用户购买商品信息也作为查询条件),此时可以使用包装对象传递输入参数。
Pojo类中包含pojo
需求:根据用户名来查询用户信息,查询条件放到QueryVo的user属性中
目的:将实体类对象user再封装一层,适用于由多个实体类对象组合成一个查询条件来实现数据的查询
在Dao接口中增加一个方法:
// 根据queryVo中的条件查询用户: 将实体类对象再封装一层,应用在由多个对象组成一个查询条件来实现数据的查询
List<User> findUserByVo(QueryVo vo);
这里的QueryVo类需要自己定义,由于这里只涉及到一个实体类对象,那么成员变量只有一个user,在domain包下定义:
public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
进而在XML文件中进行配置,注意这里的查询返回值不能再是User,由于传递进去的是vo对象,所以应该是:
<!-- 根据queryVo的条件查询用户 -->
<select id="findUserByVo" parameterType="com.huzhen.domain.QueryVo" resultType="com.huzhen.domain.User">
select * from account where username like #{user.username};
</select>
另外,在模糊查询中的#{}参数不能直接用username,因为在queryVo类中找不到username这个属性,应该由vo对象的属性user来找到username: #{user.username}.
select count(id) from account;
select * from account;
==注意:==输出简单类型必须查询出来的结果集只有一条记录,最终将第一个字段的值转换为输出类型,使用session的selectOne可查询单条记录。
注意:当查询封装结果集时,要求实体类User的属性必须与数据库中的列名保持一致,如果不一致,那么,需要修改的地方有:
public class User implements Serializable {
private Integer userId;
private String userName;
private Date userBirthday;
private String userSex;
private String userAddres;
<insert id="saveUser" parameterType="com.huzhen.domain.User"> <!-- keyProperty表示返回的值名称对应实体类USer keyColumn对应表中的id order取值为after表示插入后的行为 --> <selectKey keyProperty="id" keyColumn="id" order="AFTER" resultType="INT"> select last_insert_id(); </selectKey> insert into account(username, birthday, sex, addres) values(#{username}, #{birthday}, #{sex}, #{addres}); </insert> <update id="updateUser" parameterType="com.huzhen.domain.User"> update account set username=#{username}, birthday=#{birthday}, sex=#{sex}, addres=#{addres} where id=#{id}; </update> <delete id="deleteUser" parameterType="java.lang.Integer"> delete from account where id=#{Uid}; </delete>
除了username的属性可以得到,其他属性值均不能正确得到,因为windows中的mysql是不区分大小写的,所以将username与userName识别为同一个属性,因此可以正确返回,而其他的属性值在数据库中找不到对应的列。
那么,解决方法有:
<resultMap id="userMap" type="com.huzhen.domain.User">
<!-- 主码的配置 property对应实体类中的属性 column对应数据库的列名 -->
<id property="userId" column="id"></id>
<!-- 非主码的配置 -->
<result property="userName" column="username"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
<result property="userAddres" column="addres"></result>
</resultMap>
<!-- 注意:当使用配置来连接实体类属性名和数据库列名时候,查询xml不能再使用resultType,而是resultMap -->
<select id="findAll" resultMap="userMap">
select * from account;
</select>
其中,id为该map的名字,type依旧是实体类User;需要注意的是:在以后的sql配置中,不能在使用resultType来进行User类型的返回值的赋值,而是采用resultMap来进行返回,对应上面定义的resultMap方法名id。该种方法开发效率高,但由于会多解析一段xml配置,因此执行效率较低。
使用Mybatis开发Dao,通常有两个方法:原始Dao开发和Mapper接口代理开发。现在主流的是接口代理开发方式。
SqlSession中封装了对数据库的CRUD操作,通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder创建
自定义实现Dao的实现类来分析Mybatis是如何使用动态代理来执行sql语句的:
在Dao.impl包下创建一个Dao接口的实现类UserDaoImpl:
public class UserDaoImpl implements UserInterface{ private SqlSessionFactory factory; public UserDaoImpl(SqlSessionFactory factory){ this.factory = factory; } @Override public List<User> findAll() { SqlSession session = factory.openSession(); // factory 获取sqlsession对象,调用其中的selectList方法返回输出列表 // 传递的参数为: Dao接口的全限定类名.方法名 List<User> list = session.selectList("com.huzhen.dao.UserInterface.findAll"); session.close(); return list; } @Override public void saveUser(User user) { SqlSession session = factory.openSession(); // 对于insert方法需要多传递一个user对象 session.insert("com.huzhen.dao.UserInterface.saveUser", user); session.commit(); session.close(); } @Override public void updateUser(User user) { SqlSession session = factory.openSession(); session.update("com.huzhen.dao.UserInterface.updateUser", user); session.commit(); session.close(); } @Override public void deleteUser(Integer UserId) { SqlSession session = factory.openSession(); session.update("com.huzhen.dao.UserInterface.deleteUser", UserId); session.commit(); session.close(); } @Override public User findById(Integer userId) { SqlSession session = factory.openSession(); User user = session.selectOne("com.huzhen.dao.UserInterface.findById", userId); session.close(); return user; } @Override public List<User> findByName(String userName) { SqlSession session = factory.openSession(); List<User> list = session.selectList("com.huzhen.dao.UserInterface.findByName", userName); session.close(); return list; } @Override public int findTotalUser() { SqlSession session = factory.openSession(); Integer count = session.selectOne("com.huzhen.dao.UserInterface.findTotalUser"); return count; } }
覆盖重写Dao接口的抽象方法,在构造函数中传递一个SqlSessionFactory对象,用来创建SqlSession对象,来执行sql语句。对于查询的结果集只有一个记录,使用Session的selectOne方法(返回一个用户,或者一个聚合函数的值总数目等),如果某个方法有输入参数,那么在调用Session的方法时需要将该参数传递,如:session.insert("com.huzhen.dao.UserInterface.saveUser", user);
User user = session.selectOne("com.huzhen.dao.UserInterface.findById", userId);
。
在测试类中的实现如下:
public class test { /** * 由于需要测试多个操作,而每个test中读取配置文件,获取SqlSessionFactory来创建对象SqlSession * 以及获取Dao的代理对象和释放资源都是共用的,所以可以封装在一个init方法中 */ private static InputStream is; private static UserInterface userDao; @BeforeAll public static void init() throws IOException { is = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); // 使用工厂对象创建dao对象 userDao = new UserDaoImpl(factory); } // AfterAll和BeforeAll注解修饰的必须是静态方法 @AfterAll public static void close() throws IOException{ is.close(); } /** * 测试查询所有用户 */ @Test public void testFindAll(){ List<User> list = userDao.findAll(); for(User user : list){ System.out.println(user); } } /** * 测试新增用户 */ @Test public void testInsertUser(){ User user = new User(); user.setUsername("Bin"); user.setBirthday(new Date()); user.setSex("男"); user.setAddres("上海虹桥"); System.out.println("插入用户前:" + user); userDao.saveUser(user); // 必须需要提交事物才能添加成功,在close中执行 System.out.println("添加成功!"); System.out.println("插入用户后:" + user); } @Test public void testUpdate(){ User user = new User(); user.setId(21); user.setUsername("The shy"); user.setBirthday(new Date()); user.setSex("男"); user.setAddres("上海滴水湖"); userDao.updateUser(user); } @Test public void testDeleteUser(){ userDao.deleteUser(21); } @Test public void testSelectById(){ User user = userDao.findById(2); System.out.println(user); } @Test public void testSelectByName(){ List<User> list = userDao.findByName("%User%"); for(User user : list){ System.out.println(user); } } @Test public void testTotalUserNumber(){ int number = userDao.findTotalUser(); System.out.println("总记录数:" + number); } }
由于已经在Dao的实现类中定义了SqlSession对象,所以在测试类中的init函数中无需再根据factory来创建sqlSession,直接创建一个Dao接口的实现类对象来执行sql语句,不过在创建Dao对象时,需要传递一个factory对象。
通过debug,流程图:
当找到PreparedStatement对象时,才是JDBC中执行sql语句的方式,而关于PreparedStatement对象的执行方法有三种:
Mybatis可以使用Session.getMapper来获取Dao接口的代理对象,来帮助我们调用执行sql语句的方法,其具体流程为:
在进入到MapperProxyFactory类中的newInstance方法中可以看到使用了动态代理**Proxy.newProxyInstance,重点关注第三个参数mapper Proxy,是接口InvocationHandler的实现类,调用了invoke方法,而invoke方法中的execute方法中执行了insert,update,delete以及selectList**方法,这才是执行sql语句的方法。
SqlMapConfig.xml中配置的内容和顺序如下:
SqlMapConfig.xml可以引用java属性文件的配置信息:
jdbc.deriver=com.mysql.cj.jdbc.Driver
jdbc.url=mysql://localhost:3306/eesy?serverTimezone=UTC&useSSL=false
jdbc.username=root
jdbc.password=root
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> <properties> <!-- 配置连接数据库的4个基本信息:驱动, url, 用户名以及密码--> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/eesy?serverTimezone=UTC&useSSL=false"/> <property name="username" value="root"/> <property name="password" value="123456"/> </properties> <!-- 配置环境--> <environments default="mysql"> <!-- 配置mysql的环境--> <environment id="mysql"> <!-- 配置事务的类型--> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源(数据库连接池)--> <dataSource type="POOLED"> <!-- 配置连接数据库的4个基本信息:驱动, url, 用户名以及密码--> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <!-- 指定映射配置文件的位置,映射配置文件即为每个dao独立的配置文件 如果是使用注解来配置,此处应该使用class属性指定被注解的dao全限定类名即dao下面的接口的全类名--> <mappers> <mapper resource="com/huzhen/dao/UserDaoImpl.xml"></mapper> <!-- <mapper class="com.huzhen.domain.dao.UserInterface"></mapper>--> </mappers> </configuration>
显然,在environment中的property中的value对应着properties中的name,这样做虽然可以正确执行,但是没有必要。更多的是在properties属性,可以在标签内部配置连接数据库的信息, 也可以通过属性引用外部配置文件jdbcConfig.properties:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy?serverTimezone=UTC&useSSL=false
jdbc.username=root
jdbc.password=123456
<!-- 引用外部配置文件信息 --> <properties resource="jdbcConfig.properties"></properties> <!-- 配置环境--> <environments default="mysql"> <!-- 配置mysql的环境--> <environment id="mysql"> <!-- 配置事务的类型--> <transactionManager type="JDBC"></transactionManager> <!-- 配置数据源(数据库连接池)--> <dataSource type="POOLED"> <!-- 配置连接数据库的4个基本信息:驱动, url, 用户名以及密码--> <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>
注意property中的value不能直接写url等,而是写jdbcConfig中对应的name。
window默认地址路径是file协议,如下:
所以,在XML配置文件中的url定义为:
<properties url="file:///D:/java/Mybatis_Dao/src/main/resources/jdbcConfig.properties"></properties>
同样实现和resource加载配置文件的效果,另外在mapper中读取Dao接口配置文件的方法也可以使用url地址来读取:
<mapper url="file:///D:/java/Mybatis_Dao/src/main/resources/com/huzhen/dao/UserDaoImpl.xml"></mapper>
在SqlMapConfig.xml配置文件中加入typeAliases:
<!-- 使用typeAliases配置别名,只能配置domain中类的别名 -->
<typeAliases>
<typeAlias type="com.huzhen.domain.User" alias="user"></typeAlias>
</typeAliases>
其中,typeAlias用于配置别名,type属性指定的是domain下的实体类的全限定类名,alias为指定类名,当指定了别名就不再区分大小写,在sql语句中的resultType或者parameterType中就可以不用再写User的全限定类名,直接写user即可:
<select id="findAll" resultType="user">
select * from account;
</select>
如果,domain包下存在多个实体类,一个一个去定义比较麻烦,可以在typeAliases标签下使用package包,
<!-- 使用typeAliases配置别名,只能配置domain中类的别名 -->
<typeAliases>
<package name="com.huzhen.domain"></package>
</typeAliases>
用于指定配置别名的包,指定之后,domain包下的所有类名均是别名,不再区分大小写。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。