Mybatis 第一天学习大纲:
MyBatis 本是 apache 的一个开源项目 iBatis,2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为 MyBatis。2013年11月迁移到 Github。 MyBatis 是一个优秀的持久层框架,它对 jdbc 的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建 statement、手动设置参数、结果集检索等 jdbc 繁杂的过程代码。
Mybatis 通过 xml 或注解的方式将要执行的各种 statement(statement、preparedStatement、CallableStatement)配置起来,并通过 java 对象和 statement中 的 sql 进行映射生成最终执行的 sql 语句,最后由 Mybatis 框架执行 sql 并将结果映射成 java 对象并返回。
在学习之前我们先了解什么是 pojo、domain、po、vo、bo 及区别。
pojo:不按 mvc 分层,只是 java bean 有一些属性,还有 get、set方法
domain:不按 mvc 分层,只是 java bean 有一些属性,还有 get、set 方法
po:用在持久层,还可以再增加或者修改的时候,从页面直接传入 action 中,它里面的 java bean 类名等于表名,属性名等于表的字段名,还有对应的 get、set 方法
vo:view object 表现层对象,主要用于在高级查询中从页面接收传过来的各种参数,好处是扩展性强
bo:用在 servie 层,现在企业基本不用
相关资料: Java中PO、DO、DTO、 VO、 BO、POJO 、DAO、TO的概念
这些 po、vo、bo、pojo 可以用在各种层面吗?
可以,也就是 po 用在表现层,vo 用在持久层不报错,因为都是普通的 java bean 没有语法错误。但是在企业最好不要混着用,因为这些都是设计的原则,混着用比较乱,不利于代码维护。
先导入创建数据库的 sql 脚本导入到数据库中。
mybatis.sql:
SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `orders` -- ---------------------------- DROP TABLE IF EXISTS `orders`; CREATE TABLE `orders` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` int(11) NOT NULL COMMENT '下单用户id', `number` varchar(32) NOT NULL COMMENT '订单号', `createtime` datetime NOT NULL COMMENT '创建订单时间', `note` varchar(100) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`), KEY `FK_orders_1` (`user_id`), CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of orders -- ---------------------------- INSERT INTO `orders` VALUES ('3', '1', '1000010', '2015-02-04 13:22:35', null); INSERT INTO `orders` VALUES ('4', '1', '1000011', '2015-02-03 13:22:41', null); INSERT INTO `orders` VALUES ('5', '10', '1000012', '2015-02-12 16:13:23', null); -- ---------------------------- -- Table structure for `user` -- ---------------------------- DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL COMMENT '用户名称', `birthday` date DEFAULT NULL COMMENT '生日', `sex` char(1) DEFAULT NULL COMMENT '性别', `address` varchar(256) DEFAULT NULL COMMENT '地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of user -- ---------------------------- INSERT INTO `user` VALUES ('1', '王五', null, '2', null); INSERT INTO `user` VALUES ('10', '张三', '2014-07-10', '1', '北京市'); INSERT INTO `user` VALUES ('16', '张小明', null, '1', '河南郑州'); INSERT INTO `user` VALUES ('22', '陈小明', null, '1', '河南郑州'); INSERT INTO `user` VALUES ('24', '张三丰', null, '1', '河南郑州'); INSERT INTO `user` VALUES ('25', '陈小明', null, '1', '河南郑州'); INSERT INTO `user` VALUES ('26', '王五', null, null, null); 复制代码
表如下:
orders 表数据:
user 表数据:
因为使用的为 MySQL 数据库,使用 JDBC 操作数据库之前得先导入 mysql 的数据库驱动包 mysql-connector-java-5.1.7-bin.jar
。
加载数据库驱动
创建并获取数据库连接
创建 jdbc statement 对象
设置 sql 语句
设置 sql 语句中的参数(使用 preparedStatement)
通过 statement 执行 sql 并获取结果
对 sql 执行结果进行解析处理
释放资源(resultSet、preparedstatement、connection)
public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); //通过驱动管理类获取数据库链接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root"); //定义sql语句 ?表示占位符 String sql = "select * from user where username = ?"; //获取预处理statement preparedStatement = connection.prepareStatement(sql); //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 preparedStatement.setString(1, "王五"); //向数据库发出sql执行查询,查询出结果集 resultSet = preparedStatement.executeQuery(); //遍历查询结果集 while(resultSet.next()){ System.out.println(resultSet.getString("id")+" "+resultSet.getString("username")); } } catch (Exception e) { e.printStackTrace(); }finally{ //释放资源 if(resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(preparedStatement!=null){ try { preparedStatement.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(connection!=null){ try { connection.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 复制代码
上边使用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作。
5、jdbc 问题总结如下
① 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。 ② Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。 ③ 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。 ④ 对结果集解析存在硬编码(查询列名),sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。
1、mybatis配置
SqlMapConfig.xml,此文件作为 mybatis 的全局配置文件,配置了 mybatis 的运行环境等信息。
mapper.xml 文件即 sql 映射文件,文件中配置了操作数据库的 sql 语句。此文件需要在 SqlMapConfig.xml 中加载。
2、通过 mybatis 环境等配置信息构造 SqlSessionFactory 即 会话工厂
4、mybatis 底层自定义了 Executor 执行器接口操作数据库,Executor 接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、Mapped Statement 也是 mybatis 一个底层封装对象,它包装了 mybatis 配置信息及 sql 映射信息等。 mapper.xml 文件中一个 sql 对应一个Mapped Statement对象,sql 的 id 即是 Mapped statement 的 id。
6、Mapped Statement 对 sql 执行输入参数进行定义,包括 HashMap、基本类型、pojo,Executor 通过 MappedStatement 在执行 sql 前将输入的 java 对象映射至 sql 中,输入参数映射就是 jdbc 编程中对 preparedStatement 设置参数。
7、Mapped Statement 对 sq l执行输出结果进行定义,包括 HashMap、基本类型、pojo,Executor 通过 MappedStatement 在执行 sql 后将输出结果映射至 java 对象中,输出结果映射过程相当于 jdbc 编程中对结果的解析处理过程。
补充:
mybaits 的代码由 GitHub 管理,地址: github.com/mybatis/myb…
mybatis-3.4.6.jar----mybatis的核心包 lib----mybatis的依赖包 mybatis-3.4.6.pdf----mybatis使用手册
需求,实现以下功能:
根据用户id查询一个用户信息 根据用户名称模糊查询用户信息列表 添加用户 删除用户 更新用户 复制代码
1、Eclipse 下创建项目
2、引入 jar 包(加入 mybatis 核心包、依赖包、数据驱动包)
3、在 classpath 下创建 log4j.properties
,如下:
# Global logging configuration log4j.rootLogger=DEBUG, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n 复制代码
mybatis 默认使用 log4j 作为输出日志信息。关于 classpath 具体指哪个路径,参考网上资料: java项目里classpath具体指哪儿个路径 。
关于 classpath 路径我的理解:(以项目案例来说)
如下工程目录:
在本地磁盘下目录是这样的:
另外:在工程的 Build Path 中可以看到三个文件夹都有加入到 Build Path 中。
并且可以看到最后三个文件下生成的 class 字节码文件和配置文件都存在如上图设置的 mybatis0523/bin 目录下:
所以 classpath 路径,应该是指 Build Path 下设置的输出文件夹路径,也即加入到 Build Path 的目录。(有时间我再实践验证...)
4、在 classpath 下创建 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> <!-- 和spring整合后 environments配置将废除--> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理--> <transactionManager type="JDBC" /> <!-- 数据库连接池--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> </configuration> 复制代码
SqlMapConfig.xml
是 mybatis 核心配置文件,上边文件的配置内容为数据源、事务管理。
5、新建 PO 类,PO 类作为 mybatis 进行 sql 映射使用,PO 类通常与数据库表对应, User.java
如下:
public class User { private int id; private String username;// 用户姓名 private String sex;// 性别 private Date birthday;// 生日 private String address;// 地址 get/set 方法(略......) 复制代码
6、在 classpath 下的 config 目录下创建 sql 映射文件 Users.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="test"> </mapper> 复制代码
namespace :命名空间,用于隔离 sql 语句,后面会讲另一层非常重要的作用。
7、加载映射文件。mybatis 框架需要加载映射文件,将 Users.xml
添加到 SqlMapConfig.xml
,如下:
<mappers> <mapper resource="User.xml"/> </mappers> 复制代码
在映射文件 User.xml 中添加:
<!-- 根据id获取用户信息 --> <select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User"> select * from user where id=#{id} </select> 复制代码
id:sql 语句唯一标识
parameterType:指定传入参数类型,定义输入到 sql 中的映射类型
resultType:定义返回结果映射类型
#{}
占位符:起到占位作用,如果传入的是基本类型(string、long、double、int、boolean、float 等),那么 #{}
中的变量名称可以随意写
测试程序:
@Test public void testFindUserById() throws Exception{ String resource = "SqlMapConfig.xml"; //通过流将核心配置文件读取进来 InputStream inputStream = Resources.getResourceAsStream(resource); //通过核心配置文件输入流来创建会话工厂 SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); //通过工厂创建会话 SqlSession openSession = factory.openSession(); //第一个参数:所调用的sql语句= namespace+.+sql的ID User user = openSession.selectOne("test.findUserById", 1); System.out.println(user); openSession.close(); } 复制代码
运行结果:
在映射文件 User.xml 中添加:
<select id="findUserByUserName" parameterType="java.lang.String" resultType="cn.itheima.pojo.User"> select * from user where username like '%${value}%' </select> 复制代码
如果返回结果为集合,可以调用 selectList() 方法,这个方法返回的结果就是一个集合,所以映射文件中应该配置成集合泛型的类型。
${}
拼接符:字符串原样拼接,如果传入的参数是基本类型(string、long、double、int、boolean、float 等),那么 ${}
中的变量名称必须是 value。
注意:拼接符有 sql 注入的风险,所以慎重使用。(关于什么是 sql 注入参考网上资料,如:sql注入实例分析)
测试代码中主要的一行: List<User> list = openSession.selectList("test.findUserByUserName", "王");
---------------------------------------------- 小结 ------------------------------------------------------
#{}
表示一个占位符号,通过 #{}
可以实现 preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换, #{}
可以有效防止 sql 注入。 #{}
可以接收简单类型值或 pojo 属性值。如果 parameterType 传输单个简单类型值, #{}
括号中可以是 value 或其它名称。
${}
表示拼接 sql 串,通过 ${}
可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换, ${}
可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值, ${}
括号中只能是value。
parameterType:指定输入参数类型,mybatis 通过 ognl 从输入对象中获取参数值拼接在 sql 中。 resultType:指定输出结果类型,mybatis 将 sql 查询结果的一行记录数据映射为 resultType 指定类型的对象。
selectOne()
:查询一条记录,如果使用 selectOne 查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3 at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70) 复制代码
selectList()
:可以查询一条或多条记录。
/////////////////////////////////////////////////////////////////////////////////////////////
在映射文件 User.xml 中添加:
<!-- 未添加自增主键,可以看到最后的运行结果 id 为 0。 --> <!--<insert id="insertUser" parameterType="cn.itheima.pojo.User" > insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> --> <insert id="insertUser" parameterType="cn.itheima.pojo.User" > <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select LAST_INSERT_ID() </selectKey> insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> 复制代码
#{}
:如果传入的是 pojo 类型,那么 #{}
中的变量名称必须是 pojo 中对应的 属性.属性.属性.....
如果要返回数据库自增主键:可以使用 select LAST_INSERT_ID()
select LAST_INSERT_ID()
如果 mysql 使用 uuid 实现主键,则先把数据库主键 id 类型改为字符串 string 类型。这里需要增加通过 select uuid() 得到 uuid 值
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User"> <selectKey resultType="java.lang.String" order="BEFORE" keyProperty="id"> select uuid() </selectKey> insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address}) </insert> 复制代码
注意:这里使用的 order 是 “BEFORE”,因为先得生成一个 uuid 再把该值放入传入的 User 的 id 属性
部分测试代码:
User user = new User(); user.setUsername("赵四"); user.setBirthday(new Date()); user.setSex("1"); user.setAddress("北京昌平"); System.out.println("====" + user.getId()); openSession.insert("test.insertUser", user); //提交事务(mybatis会自动开启事务,但是它不知道何时提交,所以需要手动提交事务) openSession.commit(); 复制代码
这里解释一点:为什么测试代码中未看到开启事务却需要提交事务?这是因为 Mybatis 会自动开启事务,但不知道何时提交,所以需要手动提交事务。
在映射文件 User.xml 中添加:
<delete id="delUserById" parameterType="int"> delete from user where id=#{id} </delete> 复制代码
测试代码中主要的一行: openSession.delete("test.delUserById", 29);
在映射文件 User.xml 中添加:
<update id="updateUserById" parameterType="cn.itheima.pojo.User"> update user set username=#{username} where id=#{id} </update> 复制代码
部分测试代码:
User user = new User(); user.setId(28); user.setUsername("王麻子"); openSession.update("test.updateUserById", user); 复制代码
SqlSession 中封装了对数据库的操作,如:查询、插入、更新、删除等。
通过 SqlSessionFactory(会话工厂) 创建 SqlSession(会话),而 SqlSessionFactory 是通过 SqlSessionFactoryBuilder 进行创建。
SqlSessionFactoryBuilder 用于创建 SqlSessionFacoty,SqlSessionFacoty 一旦创建完成就不需要SqlSessionFactoryBuilder 了,因为 SqlSession 是通过 SqlSessionFactory 生产,所以可以将SqlSessionFactoryBuilder 当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。
SqlSessionFactory 是一个接口,接口中定义了 openSession 的不同重载方法,SqlSessionFactory 的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理 SqlSessionFactory。
SqlSession 是一个面向用户的接口, sqlSession 中定义了数据库操作方法。
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。
打开一个 SqlSession,使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:
SqlSession session = sqlSessionFactory.openSession(); try { // do work } finally { session.close(); } 复制代码
原始 Dao 开发方法需要程序员编写 Dao 接口和 Dao 实现类。
UserDao.java:
public interface UserDao { public User findUserById(Integer id); public List<User> findUserByUserName(String userName); } 复制代码
UserDaoImpl.java:
public class UserDaoImpl implements UserDao { private SqlSessionFactory sqlSessionFactory; //通过构造方法注入 public UserDaoImpl(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } @Override public User findUserById(Integer id) { //sqlSesion是线程不安全的,所以它的最佳使用范围在方法体内 SqlSession openSession = sqlSessionFactory.openSession(); User user = openSession.selectOne("test.findUserById", id); return user; } @Override public List<User> findUserByUserName(String userName) { SqlSession openSession = sqlSessionFactory.openSession(); List<User> list = openSession.selectList("test.findUserByUserName", userName); return list; } } 复制代码
问题:为什么不能把 SqlSession 也弄成公共的,即定义在方法体外?因为如果弄成公共的,如果来一个 User 对象,同一时间点,有人进行更新操作、有人进行删除操作,那数据就乱了。所以需要放在方法体内,用完了就销毁了,其他人用不了。
测试代码 UserDaoTest.java:
public class UserDaoTest { private SqlSessionFactory factory; //作用:在测试方法前执行这个方法 @Before public void setUp() throws Exception{ String resource = "SqlMapConfig.xml"; //通过流将核心配置文件读取进来 InputStream inputStream = Resources.getResourceAsStream(resource); //通过核心配置文件输入流来创建会话工厂 factory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindUserById() throws Exception{ //将初始化好的工厂注入到实现类中 UserDao userDao = new UserDaoImpl(factory); User user = userDao.findUserById(1); System.out.println(user); } @Test public void testFindUserByUserName () throws Exception{ UserDao userDao = new UserDaoImpl(factory); List<User> list = userDao.findUserByUserName("王"); System.out.println(list); } } 复制代码
Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 Dao 接口),由 Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边 Dao 接口实现类方法。
Mapper接口开发需要遵循以下规范:
定义 mapper 映射文件 UserMapper.xml(内容同 Users.xml),需要修改 namespace 的值为 UserMapper 接口路径。将 UserMapper.xml 放在classpath 下 mapper目录下。
<?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接口代理实现编写规则: 1. 映射文件中namespace要等于接口的全路径名称 2. 映射文件中sql语句id要等于接口的方法名称 3. 映射文件中传入参数类型要等于接口方法的传入参数类型 4. 映射文件中返回结果集类型要等于接口方法的返回值类型 --> <mapper namespace="cn.itheima.mapper.UserMapper"> <!-- id:sql语句唯一标识 parameterType:指定传入参数类型 resultType:返回结果集类型 #{}占位符:起到占位作用,如果传入的是基本类型(string,long,double,int,boolean,float等),那么#{}中的变量名称可以随意写. --> <select id="findUserById" parameterType="int" resultType="cn.itheima.pojo.User"> select * from user where id=#{id} </select> <!-- 如果返回结果为集合,可以调用selectList方法,这个方法返回的结果就是一个集合,所以映射文件中应该配置成集合泛型的类型 ${}拼接符:字符串原样拼接,如果传入的参数是基本类型(string,long,double,int,boolean,float等),那么${}中的变量名称必须是value 注意:拼接符有sql注入的风险,所以慎重使用 --> <select id="findUserByUserName" parameterType="string" resultType="user"> select * from user where username like '%${value}%' </select> <!-- #{}:如果传入的是pojo类型,那么#{}中的变量名称必须是pojo中对应的属性.属性.属性..... 如果要返回数据库自增主键:可以使用select LAST_INSERT_ID() --> <insert id="insertUser" parameterType="cn.itheima.pojo.User" > <!-- 执行 select LAST_INSERT_ID()数据库函数,返回自增的主键 keyProperty:将返回的主键放入传入参数的Id中保存. order:当前函数相对于insert语句的执行顺序,在insert前执行是before,在insert后执行是AFTER resultType:id的类型,也就是keyproperties中属性的类型 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select LAST_INSERT_ID() </selectKey> insert into user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address}) </insert> </mapper> 复制代码
3、UserMapper.java(接口文件)
public interface UserMapper { public User findUserById(Integer id); //动态代理形势中,如果返回结果集问List,那么mybatis会在生成实现类的使用会自动调用selectList方法 public List<User> findUserByUserName(String userName); public void insertUser(User user); } 复制代码
接口定义有如下特点:
修改 SqlMapConfig.xml 文件:
<!-- 加载映射文件 --> <!-- 使用class属性引入接口的全路径名称: 使用规则: 1. 接口的名称和映射文件名称除扩展名外要完全相同 2. 接口和映射文件要放在同一个目录下 --> <mappers> <mapper resource="cn.itheima.mapper.UserMapper"/> </mappers> 复制代码
补充:如果有很多 mapper 映射文件要加载呢,则每个都写上很麻烦,使用包扫描的方式批量引入Mapper 接口,如下:
<!-- 使用包扫描的方式批量引入Mapper接口 使用规则: 1. 接口的名称和映射文件名称除扩展名外要完全相同 2. 接口和映射文件要放在同一个目录下 --> <package name="cn.itheima.mapper"/> 复制代码
public class UserMapperTest { private SqlSessionFactory factory; //作用:在测试方法前执行这个方法 @Before public void setUp() throws Exception{ String resource = "SqlMapConfig.xml"; //通过流将核心配置文件读取进来 InputStream inputStream = Resources.getResourceAsStream(resource); //通过核心配置文件输入流来创建会话工厂 factory = new SqlSessionFactoryBuilder().build(inputStream); } @Test public void testFindUserById() throws Exception{ SqlSession openSession = factory.openSession(); //通过getMapper方法来实例化接口 UserMapper mapper = openSession.getMapper(UserMapper.class); User user = mapper.findUserById(1); System.out.println(user); } @Test public void testFindUserByUserName() throws Exception{ SqlSession openSession = factory.openSession(); //通过getMapper方法来实例化接口 UserMapper mapper = openSession.getMapper(UserMapper.class); List<User> list = mapper.findUserByUserName("王"); System.out.println(list); } @Test public void testInsertUser() throws Exception{ SqlSession openSession = factory.openSession(); //通过getMapper方法来实例化接口 UserMapper mapper = openSession.getMapper(UserMapper.class); User user = new User(); user.setUsername("老王"); user.setSex("1"); user.setBirthday(new Date()); user.setAddress("北京昌平"); mapper.insertUser(user); openSession.commit(); } } 复制代码
补充:
☛ selectOne和selectList
动态代理对象调用 sqlSession.selectOne()
和 sqlSession.selectList()
是根据 mapper 接口方法的返回值决定,如果返回 list 则调用 selectList 方法,如果返回单个对象则调用 selectOne 方法。
☛ namespace
mybatis 官方推荐使用 mapper 代理方法开发 mapper 接口,程序员不用编写 mapper 接口实现类,使用 mapper 代理方法时,输入参数可以使用 pojo 包装对象或 map 对象,保证 dao 的通用性。
SqlMapConfig.xml 中配置的内容和顺序如下:
properties(属性) settings(全局配置参数) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境集合属性对象) environment(环境子属性对象) transactionManager(事务管理) dataSource(数据源) mappers(映射器) 复制代码
SqlMapConfig.xml 可以引用 java 属性文件中的配置信息如下:
在 classpath下定义 db.properties
文件:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8 jdbc.username=root jdbc.password=root 复制代码
SqlMapConfig.xml 引用如下:
<properties resource="db.properties"></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> 复制代码
注意,MyBatis 将按照下面的顺序来加载属性
mybatis 支持的别名:
类型别名什么意思呢?在如下映射文件中,parameterType 的值之前写的是 java.lang.Integer
:
<select id="findUserById" parameterType="java.lang.Integer" resultType="cn.itheima.pojo.User"> select * from user where id=#{id} </select> 复制代码
但其实可以写为 Mybatis 支持的别名 int:
<select id="findUserById" parameterType="int" resultType="cn.itheima.pojo.User"> select * from user where id=#{id} </select> 复制代码
如果碰到 cn.itheima.pojo.User
呢? ---> 可以自定义别名。如下:
在 SqlMapConfig.xml 中配置:
<typeAliases> <!-- 单个别名定义 type:类的全路劲名称 alias:别名 --> <typeAlias alias="user" type="cn.itcast.mybatis.po.User"/> </typeAliases> 复制代码
如果有很多这样类需要别名定义呢?可以使用包扫描方式:
<typeAliases> <!-- 使用包扫描的方式批量定义别名,如下,会自动扫描cn.itheima.pojo下的所有pojo 定义后别名等于类名,不区分大小写,但是建议按照java命名规则来,首字母小写,以后每个单词的首字母大写(驼峰式命名) --> <package name="cn.itheima.pojo"/> <!--<package name="其它包"/> --> </typeAliases> 复制代码
注: ①定义后别名等于类名并且不区分大小写 ;②这里所说不区分大小写什么意思呢? 如下例
<select id="findUserById" parameterType="java.lang.Integer" resultType="UserR"> select * from user where id=#{id} </select> 复制代码
resultType 的值写为 UseR 也没问题的。
Mapper 配置的几种方法:
<mapper resource=" " />
使用相对于类路径的资源
如: <mapperresource="sqlmap/User.xml" />
1.1.1 <mapper class=" " />
使用mapper接口类路径
如: <mapperclass="cn.itcast.mybatis.mapper.UserMapper"/>
注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。
<package name=""/>
注册指定包下的所有 mapper 接口
如: <packagename="cn.itcast.mybatis.mapper"/>
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。
<!-- 使用包扫描的方式批量引入Mapper接口 使用规则: 1. 接口的名称和映射文件名称除扩展名外要完全相同 2. 接口和映射文件要放在同一个目录下 --> <package name="cn.itheima.mapper"/> 复制代码
1、数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。
2、sql 语句写在代码中造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变 java 代码。
解决:将 Sql 语句配置在 XXXXmapper.xml
文件中与 java 代码分离。
3、向 sql 语句传参数麻烦,因为 sql 语句的 where 条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型。
4、对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。
解决:Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的类型。
Mybatis 和 Hibernate 不同,它不完全是一个 ORM 框架,因为 MyBatis 需要程序员自己编写 sql 语句,不过mybatis 可以通过 XML 或注解方式灵活配置要运行的 sql 语句,并将 java 对象和 sql 语句映射生成最终执行的 sql,最后将 sql 执行的结果再映射生成 java 对象。
Mybatis 学习门槛低,简单易学,程序员直接编写原生态 sql,可严格控制 sql 执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是 mybatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 sql 映射文件,工作量大。
Hibernate 对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用 Hibernate 开发可以节省很多代码,提高效率。但是 Hibernate 的学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。
小总结:
1. mybatis是一个持久层框架, 作用是跟数据库交互完成增删改查 2.原生Dao实现(需要接口和实现类) 4.动态代理方式(只需要接口) mapper接口代理实现编写规则: 1) 映射文件中namespace要等于接口的全路径名称 2) 映射文件中sql语句id要等于接口的方法名称 3) 映射文件中传入参数类型要等于接口方法的传入参数类型 4) 映射文件中返回结果集类型要等于接口方法的返回值类型 5. #{}占位符:占位 如果传入的是基本类型,那么#{}中的变量名称可以随意写 如果传入的参数是pojo类型,那么#{}中的变量名称必须是pojo中的属性.属性.属性... 6. ${}拼接符:字符串原样拼接 如果传入的是基本类型,那么${}中的变量名必须是value 如果传入的参数是pojo类型,那么${}中的变量名称必须是pojo中的属性.属性.属性... 注意:使用拼接符有可能造成sql注入,在页面输入的时候可以加入校验,不可输入sql关键字,不可输入空格 7. 映射文件: 1)传入参数类型通过parameterType属性指定 2)返回结果集类型通过resultType属性指定 8. hibernate和mybatis区别: hibernate:它是一个标准的orm框架,比较重量级,学习成本高. 优点:高度封装,使用起来不用写sql,开发的时候,会减低开发周期. 缺点:sql语句无法优化 应用场景:oa(办公自动化系统), erp(企业的流程系统)等,还有一些政府项目, 总的来说,在用于量不大,并发量小的时候使用. mybatis:它不是一个orm框架, 它是对jdbc的轻量级封装, 学习成本低,比较简单 有点:学习成本低, sql语句可以优化, 执行效率高,速度快 缺点:编码量较大,会拖慢开发周期 应用场景: 互联网项目,比如电商,P2p等 总的来说是用户量较大,并发高的项目. 复制代码