2017.2.18-2017.2.23
上次跟书里学的mybatis,好像我学了个假的mybatis,现在跟一个视屏学
第一个例子
- 导入jar包,将mybatis文件夹下的所有jar包导入,还有数据库的
创建mybatis的配置文件,名称位置任意,如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 resource="db.properties"/> <!-- 加上配置路径 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="url" value="${url}" /> <property name="driver" value="${drivername}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> </dataSource> </environment> </environments> <!-- 加载映射文件 --> <mappers> <mapper resource="sqlmap/mapper.xml"/> </mappers> </configuration>
将数据库的配置写入db.properties中
url=jdbc:mysql:///mybatis drivername=com.mysql.jdbc.Driver username=root password=root
创建pojo类,如User
创建映射文件,如: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" > <!-- namespace命名空间,作用就是对SQL进行分类化管理 --> <mapper namespace="zcw"> <!-- 在映射文件中配置很多SQL语句 --> <!-- 通过select执行数据库查询 id:标识映射文件中的sql 将SQL语句封装到Statement对象中,所以将id称为statement的id、 #{}标识一个占位符 --> <select id="findUserById" parameterType="int" resultType="cn.zcw.po.User"> select * from user where id=#{id} </select> </mapper>
写测试类进行测试,如通过id查询
@Test public void findByIdTest() throws IOException{ //mybatis配置文件 String resource="SqlMapConfig.xml"; //配置文件流 InputStream inputStream=Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); //通过工厂得到SqlSession SqlSession sqlSession=sqlSessionFactory.openSession(); //通过SqlSession操作数据库 //第一个参数:映射文件中的namespace+statement的id //第二个参数:指定和映射文件中匹配的parameterType类型 User user=sqlSession.selectOne("zcw.findUserById", 6); System.out.println(user); }
根据字段模糊查询数据
在mybatis的映射文件中配置SQL语句
<!-- 根据name模糊查询user --> <!-- ${}表示拼接sql串,将接收到的参数内容不加修饰的拼接在sql中 但是这样可能会引起sql注入 ${value}:接收参数的内容,如果传入类型是简单类型,${}中只能使用value --> <select id="findUserByName" parameterType="String" resultType="cn.zcw.po.User"> select * from user where username like '%${value}%' </select>
单元测试(核心代码)
List<User> users=sqlSession.selectList("zcw.findUserByName", "z");
#{}和${}
#{},表示一个占位符
#{}接收输入参数,类型可以是简单类型、pojo、hashmap。
如果接收简单类型,#{}仲可以写成value或其他名称。
#{}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值。
${},表示字符串连接,会引起sql注入,不建议使用
${}接受参数,类型可以是简单类型、pojo、hashmap
如果接收简单类型,${}中只能写成value
${}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值。
插入操作
在映射文件中配置添加用户的statement
<!-- 添加用户 parameterType:指定输入参数类型是pojo ${}中指定pojo的属性名 --> <insert id="insertUser" parameterType="cn.zcw.po.User"> insert into user(id,username,password) values(#{id},#{username},#{password}) </insert>
单元测试(注意:最后要提交)
User user=new User(); user.setUsername("张三"); user.setPassword("password"); sqlSession.insert("zcw.insertUser",user); //提交事务 sqlSession.commit();
获得刚插入的id
主键自动生成
配置映射文件
<insert id="insertUser" parameterType="cn.zcw.po.User"> <!-- 将插入数据的主键返回,返回到user对象中 select last_insert_id():得到刚insert进去记录的主键值,只使用与自增主键 keyProperty:将查询到主键值设置到parameterType指定的对象的那个属性 order:selecr last_insert_id():执行顺序,相对于insert语句 resultType:select last_insert_id()返回的类型 --> <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer"> select last_insert_id() </selectKey> insert into user(id,username,password) values(#{id},#{username},#{password}) </insert>
单元测试
User user=new User(); user.setUsername("张三"); user.setPassword("password"); sqlSession.insert("zcw.insertUser",user); System.out.println(user.getId()); //提交事务 sqlSession.commit();
uuid主键
配置映射文件
<!-- 使用mysql的uuid()生成主键 执行过程: 首先通过uuid()得到主键,将主键设置到user对象的id属性中 其次在insert执行时,从user对象中取出id属性值 --> <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String"> select uuid() </selectKey>
删除操作
在映射文件中配置删除的statement
<delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id=#{id} </delete>
- 测试代码(注意:要commit)
更新操作
在映射文件中配置更新的statement
<!-- 更新操作 --> <update id="updateUser" parameterType="cn.zcw.po.User"> update user set username=#{username},password=#{password} where id=#{id} </update>
测试代码(注意:要commit)
User user=new User(); user.setId(1); user.setPassword("password"); user.setUsername("张常文"); sqlSession.update("zcw.updateUser",user); sqlSession.commit();
hibernate和mybatis的应用场景
hibernate:是一个标准ORM框架。入门门槛较高,不需要写SQL语句。但是对SQL语句进行优化、修改比较困哪。应用场景:适用于需求变化不多的中小型项目,比如:后台管理系统,erp,orm,oa
mybatis:专注SQL本身,需要程序员自己编写sql语句,SQL修改、优化比较方便。mybatis是一个不完全的ORM框架。虽然程序员自己写SQL,mybatis也可以实现映射(输入映射、输出映射)。应用场景:使用与需求变化较多的项目,比如:互联网项目。
SqlSession、SqlSessionFactory、SqlSessionFactoryBuilder
SqlSession是一个面向用户的接口,他是线程不安全的,SqlSession最佳应用场合是在方法体内,定义成局部变量使用
SqlSessionFactory只需创建一次,所以使用单例模式
- SqlSessionFactoryBuilder用工具类获取
原始dao的应用
创建UserDao接口
public interface UserDao { public User findUserById(Integer id); public void insertUser(User user); public void deleteUser(Integer id); }
创建类UserDaoImpl类实现UserDao接口,并注入SqlSessionFactory对象
public class UserDaoImpl implements UserDao{
private SqlSessionFactory sqlSessionFactory=null;public UserDaoImpl(SqlSessionFactory sqlSessionFactory){ this.sqlSessionFactory=sqlSessionFactory; } @Override public User findUserById(Integer id) { //需要将SqlSession放到方法体内,因为其不是线程安全类 SqlSession sqlSession=sqlSessionFactory.openSession(); User user=sqlSession.selectOne("zcw.findUserById",id); return user; } @Override public void insertUser(User user) { SqlSession sqlSession=sqlSessionFactory.openSession(); sqlSession.insert("zcw.insertUser",user); sqlSession.commit(); } @Override public void deleteUser(Integer id) { // TODO Auto-generated method stub SqlSession sqlSession=sqlSessionFactory.openSession(); sqlSession.delete("zcw.deleteUser",id); } }
创建测试类测试,向UserDao中注入SqlSessionFactory对象
public class test { private SqlSessionFactory sqlSessionFactory; //@Before会在@Test之前执行 @Before public void init() throws IOException{ String resource="SqlMapConfig.xml"; InputStream inputStream=Resources.getResourceAsStream(resource); sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream); } @Test public void test(){ UserDao userDao=new UserDaoImpl(sqlSessionFactory); User user=userDao.findUserById(2); System.out.println(user); } }
mapper.xml开发规范
- 在mapper.xml中的namespace等于mapper.java接口的全路径
- mapper.java接口中的方法输入参数类型和mapper.xml中statement的id一致
- mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致
- mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致
selectOne和selectList
- selectOne表示查询出一条记录进行映射。如果使用selectOne可以实现,使用selectList也可以实现
- selectList表示查询出一个列表(多条记录)进行映射。如果使用selectList查询多条记录,不能使用selectOne。如果使用selectOne会报错
mapper接口方法参数只能有一个是否影像系统开发
即使mapper接口只有一个参数,可以适用包装类型的pojo满足不同的业务方法的需求
SqlMapConfig.xml
- properties属性,可以将数据库的配置写到db.properties文件中,通过此属性读取
typeAliases(别名)
在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输入结果的映射类型。要定义在environments标签之前。
<!-- 定义别名 --> <typeAliases> <!-- 定义单个别名 type:类的全路径 alias:别名 --> <!-- <typeAlias type="cn.zcw.po.User" alias="user"/> --> <!-- 批量定义别名 别名就是类名 --> <package name="cn.zcw.po"/> </typeAliases>
mappers
resource
<mapper resource="sqlmap/mapper.xml"/>
class
<!-- 通过mapper接口加载单个映射文件 规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在同一目录下 前提:使用mapper代理的方法 --> <mapper class="cn.zcw.mapper.UserMapper"/>
批量加载
<!-- 批量加载 规范:需要将mapper接口类名和mapper.xml映射文件名称保持一致,且在同一目录下 前提:使用mapper代理的方法 --> <package name="cn.zcw.mapper"/>
输入映射–pojo包装类型实现
- 创建UserCustom.java类继承User(扩展User)
- 创建USerVo.java类包含UserCustom对象属性,并生成其get、set方法
在映射文件中配置statement
<select id="findUser" parameterType="cn.zcw.po.UserVo" resultType="cn.zcw.po.UserCustom"> select * from user where username=#{userCustom.username} </select>
- 在mapper接口类中声明对应的findUser方法
测试
//调用userMapper对象的方法 UserVo userVo=new UserVo(); UserCustom userCustom=new UserCustom(); userCustom.setUsername("张常文"); userVo.setUserCustom(userCustom); UserCustom user=userMapper.findUser(userVo); System.out.println(user);
resultType
使用resultType进行输出映射,只有查询出来的列明和pojo中的属性名一致,该列才可以映射成功
resultMap
<!-- 定义resultMap
将select id id_,username username_,password password_ from user 和User类中的属性做一个映射关系
type:resultMap最终映射的Java对象类型,可以使用别名
id:对resultMap的唯一标识
-->
<resultMap type="User" id="userResultMap">
<!-- id表示查询结果集中的唯一标识
colume:查出来的列名
property:type指定的pojo类型中的属性名
-->
<id column="id_" property="id"/>
<!-- result:对普通名称映射定义
column:查询出来的列名
property:type指定pojo类型中的属性名
-->
<result column="username_" property="username"/>
<result column="password_" property="password"/>
</resultMap>
<!-- resultMap为resultMap的id -->
<select id="findUserMap" parameterType="cn.zcw.po.UserVo" resultMap="userResultMap">
select id id_,username username_,password password_ from user
</select>
动态sql
mybatis核心对SQL语句灵活操作,通过表达式进行判断,对SQL进行灵活拼接
sql片段
<sql id="userinfoField">id,name,password</sql>
<select id="getUserinfoAll" resultType="map">
select
<include refid="userinfoField"/>
form
userinfo
</select>
一对一查询–resultType
描述:一个订单只属于一个用户,一对一关系。订单表中有一个外键对应用户表中的id
例子:查询订单的基本信息和对应的用户信息
- 创建user表对应的User实体类,创建UserCustom类继承User类并且创建Order表对应的属性
实现映射关系表。返回类型为UserCustom
<select id="selectUserOrder" resultType="UserCustom"> select `user`.*,number from user,orders WHERE `user`.id=orders.user_id </select>
- 在mapper接口中实现相应的方法
一对一查询–resultMap
描述、例子与上述相同
- 创建Order实体类,在Order类中声明User对象
创建resultMap
<resultMap type="cn.zcw.mapper.Order" id="userOrder"> <!-- 配置映射的订单信息 id:查询列中的唯一标识,这里就是orders的id列 --> <id column="id" property="id"/> <result column="number" property="number"/> <!-- 配置映射的关联用户信息 peoperty:映射到order类中的哪个属性 --> <association property="user" javaType="cn.zcw.mapper.User"> <!-- id:关联用户的唯一标识 --> <id column="user_id" property="id"/> <result column="username" property="username"/> <result column="password" property="password"/> </association> </resultMap>
- 创建statement,resultMap等于userOrder
一对多查询
例子:查询订单-订单明细,一对多的查询,顺带查询出对应用户
- 创建Items实体类
- 创建Orders类,属性对应order表,user属性,List属性
构件resultMap(因为其也需查询出user和order的信息,所以可以继承上面的userOrder)
<!-- 配置订单查询订单明细及用户信息的一对多resultMap --> <resultMap type="cn.zcw.mapper.Orders" id="userOrderItems" extends="userOrder"> <collection property="items" ofType="cn.zcw.mapper.Items"> <!-- id:items的唯一标识 --> <id column="items_id" property="id"/> <result column="itemname" property="itemname"/> <result column="orders_id" property="orders_id"/> </collection> </resultMap>
多对多查询
例子:查询用户对商品,商品对明细是一对多,所以用户对商品为多对多关系。
- 在user实体类中声明List属性,在Order实体类中声明List属性,在OrderDetail中声明Items属性
配置resultMap
<resultMap type="cn.zcw.mapper.User" id="userItems"> <id column="id" property="id"/> <result></result> <!-- 一个用户对应多个订单 --> <collection property="orderList" ofType="cn.zcw.mapper.Order"> <id column="order_id" property="id"/> <!-- 一个订单对应多个订单明细 --> <collection property="orderDetailList" ofType="cn.zcw.mapper.OrderDetail"> <id column="orderDetail_id" property="id"/> <!-- 一个订单明细对应一个商品 --> <association property="items" javaType="cn.zcw.mapper.Items"> <id column="items_id" property="id"/> </association> </collection> </collection> </resultMap>
延迟查询
select order.*,user.* from order,user where order.userid=user.id作为延迟查询先select * from order单表查询,当Order对象获得user时再来发送SQL查询相应的user对象
- 创建order类,包含user对象属性
创建statement,单表查询
<select id="findUserOrderLazyLoding" resultMap="UserOrderLazyLodingResultMap"> select * from orders </select>
创建resultMap
<!-- 延迟加载的resultMap --> <resultMap type="cn.zcw.mapper.Order" id="UserOrderLazyLodingResultMap"> <!-- 配置映射的订单信息 --> <id column="id" property="id"/> <result column="number" property="number"/> <result column="user_id" property="user_id"/> <!-- select值为完成order.userid=user.id的statement的id,column为查询条件 --> <association property="user" javaType="cn.zcw.mapper.User" select="findUserById" column="user_id"> </association> </resultMap> <select id="findUserById" parameterType="java.lang.Integer" resultType="cn.zcw.mapper.User"> select * from user where id=#{value} </select>
在SqlMapConfig.xml中配置延迟加载
<settings> <!-- 将延迟加载开关打开 --> <setting name="lazyLoadingEnabled" value="true"/> <!-- 开启延迟加载 --> <setting name="aggressiveLazyLoading" value="false"/> </settings>
测试
SqlSession sqlSession=sqlSessionFactory.openSession(); UserMapper userMapper=sqlSession.getMapper(UserMapper.class); List<Order> orders=userMapper.findUserOrderLazyLoding(); for (Order order : orders) { //查询相应的user System.out.println(order.getUser()); }
查询缓存
mybatis提供查询缓存,用于减轻数据库压力。mybatis提供一级缓存和二级缓存。
一级缓存
一级缓存是SqlSession级别的缓存。在操作数据库是需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互不影响的
二级缓存
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
一级缓存
默认打开
二级缓存
在SQLMapConfig.xml文件中将二级缓存打开
<!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/>
二级缓存的作用范围是Mapper的namespace下的,所以需要在Mapper文件中将二级缓存打开
<!-- 开启二级缓存 --> <cache></cache>
应用场景:请求次数多且用户对查询的实时性要求不高,如:耗时高的的统计分析SQL、电话账单查询等
实现方法:设置刷新间隔时间flushinterval
局限性:二级缓存对细粒度的数据级别的缓存实现不好
statement中禁止使用二级缓存
在statement中设置useCache=false可以禁用到钱select语句的二级缓存,即每次查询都会发出SQL去查询,默认情况是TRUE
<select id="findUserOrderLazyLoding" resultMap="UserOrderLazyLodingResultMap" useCache="false">
刷新缓存
在select、update、insert执行(commit)的后需要执行刷新缓存(即清空缓存),否则可能会读到脏数据。
mybatis和Spring整合–原始dao开发
- 导入jar包,包括mybatis-spring.jar(注意版本号)
在Spring配置文件中配置SqlSessionFactory,和dao
<!-- 加载配置文件 --> <context:property-placeholder location="classpath:db.properties" /> <!-- 配置c3p0数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 注入属性值 --> <property name="driverClass" value="${jdbc.drivername}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" > <!-- 加载mybatis配置文件 --> <property name="configLocation" value="mybatis/SqlMapConfig.xml"></property> <!-- 配置数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置UserDao --> <bean id="userDao" class="cn.zcw.ssm.dao.UserDaoImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> </bean>
UserDaoImpl代码
SqlSession sqlSession=this.getSqlSession(); User user=sqlSession.selectOne("test.findUserById", id); return user; }
spring-mybatis整合–mapper代理开发
逐个配置mapper
<!-- mapper配置 此方法需要对每一个mapper进行配置,麻烦 --> <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> mapperInterface指定mapper接口 <property name="mapperInterface" value="cn.zcw.mapper.UserMapper"></property> <property name="sqlSessionFactory" ref="sqlSessionFactory"></property> </bean>
扫描mapper(建议)
<!-- mapper批量扫描,从mapper包中扫描出mapper接口,自动创建代理对象并且在Spring容器中注册 遵循规范:将mapper.java和mapper.xml映射文件的名称保持一致,且在同一目录 自动扫描出来的mapper的bean的id为mapper类名,首字母小写 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- basePackage: 要扫描的包名,如果要扫描多个包,可用逗号隔开 --> <property name="basePackage" value="cn.zcw.mapper"/> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> </bean>