原始jdbc开发存在的问题如下:
数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变java代码。查询操作时,需要手动将结果集中的数据手动封装到实体中。插入操作时,需要手动将实体的数据设置到sql语句的占位符位置应对上述问题给出的解决方案:
使用数据库连接池初始化连接资源将sql语句抽取到xml配置文件中使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射MyBatis开发步骤:
添加MyBatis的坐标 <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> 创建user数据表编写User实体类编写映射文件UserMapper.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="userMapper"> <select id="findAll" resultType="com.itheima.domain.User"> select * from user </select> </mapper>映射文件的简单说明:
编写核心文件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> <!--数据源环境--> <environments default="developement"> <environment id="developement"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root"/> <property name="password" value="hc199854"/> </dataSource> </environment> </environments> <!--加载映射文件--> <mappers> <mapper resource="com/itheima/mapper/UserMapper.xml"></mapper> </mappers> </configuration> 编写测试类 @Test //查询操作 public void test1() throws IOException { //获得核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //获得session工厂对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获得session回话对象 SqlSession sqlSession = sqlSessionFactory.openSession(); //执行操作 参数:namespace+id List<User> userList = sqlSession.selectList("userMapper.findAll"); //打印数据 System.out.println(userList); //释放资源 sqlSession.close(); }增删改查的简单示范:
<!--删除操作 注意当参数是基本数据类型时,{}里的变量名可以时任意的--> <delete id="delete" parameterType="int"> delete from user where id=#{abc} </delete> <!--修改操作--> <update id="update" parameterType="com.itheima.domain.User"> update user set username=#{username},password=#{password} where id=#{id} </update> <!--插入操作 注意占位符填写的是实体的属性名--> <insert id="save" parameterType="com.itheima.domain.User"> insert into user values(#{id},#{username},#{password}) </insert> <!--查询操作--> <select id="findAll" resultType="com.itheima.domain.User"> select * from user </select> <!--根据id进行查询--> <select id="findById" resultType="user" parameterType="int"> select * from user where id=#{id} </select>除了进行查询操作时,要注意提交事务: sqlSession.commit(); MyBatis的事务默认是不提交的,需要我们手动提交。
MyBatis核心配置文件层级关系:
1.environments标签: 数据库环境的配置,支持多环境配置 其中,事务管理器(transactionManager)类型有两种:
JDBC:这个配置就是直接使用了JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。其中,数据源(dataSource)类型有三种:
UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。JNDI:这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。2.mapper标签: 该标签的作用是加载映射的,加载方式有如下几种:
使用相对于类路径的资源引用,例如: <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>使用完全限定资源定位符(URL),例如: <mapper url="file:///var/mappers/AuthorMapper.xml"/>使用映射器接口实现类的完全限定类名,例如: <mapper class="org.mybatis.builder.AuthorMapper"/>将包内的映射器接口实现全部注册为映射器,例如: <package name="org.mybatis.builder"/>3.Properties标签: 实际开发中,习惯将数据源的配置信息单独抽取成一个properties文件,该标签可以加载额外配置的properties文件。
4.typeAliases标签:
类型别名是为Java 类型设置一个短的名字。原来的类型名称配置如下 配置typeAliases,为com.itheima.domain.User定义别名为user
上面我们是自定义的别名,mybatis框架已经为我们设置好的一些常用的类型的别名
SqlSession工厂构建器SqlSessionFactoryBuilder:
常用API:SqlSessionFactory build(InputStream inputStream) 通过加载mybatis的核心文件的输入流的形式构建一个SqlSessionFactory对象
String resource = "org/mybatis/builder/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(inputStream);其中, Resources 工具类,这个类在 org.apache.ibatis.io 包中。Resources 类帮助你从类路径下、文件系统或一个web URL 中加载资源文件。
SqlSession工厂对象SqlSessionFactory:
SqlSessionFactory 有多个个方法创建 SqlSession 实例。常用的有如下两个:
SqlSession会话对象: SqlSession 实例在 MyBatis 中是非常强大的一个类。在这里你会看到所有执行语句、提交或回滚事务和获取映射器实例的方法。 执行语句的方法主要有:
<T> T selectOne(String statement, Object parameter) <E> List<E> selectList(String statement, Object parameter) int insert(String statement, Object parameter) int update(String statement, Object parameter) int delete(String statement, Object parameter)操作事务的方法主要有:
void commit() //提交事务 void rollback() //回滚事务传统的MyBatis接口实现需要创建接口,然后创建接口的实现类(Impl)。 使用接口代理方式就不需要实现impl,MyBatis框架会根据定义的接口实现接口的实现类,并且MyBatis框架会帮助创建实现类对象。
Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口) Mapper 接口开发需要遵循以下规范:
Mapper.xml文件中的namespace与mapper接口的全限定名相同Mapper接口方法名和Mapper.xml中定义的每个statement的id相同Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同示意图如下:
注意:resultType如果没有没有定义别名需要写全限定名。
//获得MyBatis框架生成的UserMapper接口的实现类 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。
<if>语句: 我们根据实体类的不同取值,使用不同的 SQL语句来进行查询。比如在 id如果不为空时可以根据id查询,如果username不同空时还要加入用户名作为条件。这种情况在我们的多条件组合查询中经常会碰到。
<select id="findByCondition" resultType="com.itheima.domain.User"> select * from user <where> <if test="id!=0"> and id=#{id} </if> <if test="username!=null"> and username=#{username} </if> <if test="password!=null"> and password!=#{password} </if> </where> </select>当查询条件id和username都存在时,控制台打印的sql语句如下:
当查询条件只有id存在时,控制台打印的sql语句如下: 使用标签,如果所有条件都不成立时,就不会添加where语句。就变成了查询所有数据。
<foreach>语句: 循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,3)。
<select id="findByIds" parameterType="list" resultType="com.itheima.domain.User"> select * from user <where> <foreach collection="list" open="id in(" close=")" item="id" separator=","> #{id} </foreach> </where> </select>测试代码片段:
UserMapper mapper = sqlSession.getMapper(UserMapper.class); ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); List<User> res = mapper.findByIds(list); System.out.println(res);foreach标签的属性含义如下:
collection:代表要遍历的集合元素,注意编写时不要写#{}open:代表语句的开始部分close:代表结束部分item:代表遍历集合的每个元素,生成的变量名sperator:代表分隔符Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的。
知识小结:
<select>:查询 <insert>:插入 <update>:修改 <delete>:删除 <where>:where条件 <if>:if判断 <foreach>:循环 <sql>:sql片段抽取无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器(截取部分)。
你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个JDBC类型。例如需求:一个Java中的Date数据类型,我想将之存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的varchar毫秒值之间转换。
开发步骤:
定义转换类继承类BaseTypeHandler<T>覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法(将Java数据类型转换为数据库数据),getNullableResult为查询时 mysql的字符串类型转换成 java的Type类型的方法(将数据库的数据转换为Java数据类型)。 //泛型填写需要转换的Java类型 public class DateTypeHandler extends BaseTypeHandler<Date> { //将java类型 转换成 数据库需要的类型 //参数int i表示占位符(参数)的位置 public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException { long time = date.getTime(); preparedStatement.setLong(i,time); } //将数据库中类型 转换成java类型 //String参数 要转换的字段名称 //ResultSet 查询出的结果集 public Date getNullableResult(ResultSet resultSet, String s) throws SQLException { //获得结果集中需要的数据(long) 转换成Date类型 返回 long aLong = resultSet.getLong(s); Date date = new Date(aLong); return date; } //将数据库中类型 转换成java类型 //参数int i表示数据再结果集resultSet中的位置 public Date getNullableResult(ResultSet resultSet, int i) throws SQLException { long aLong = resultSet.getLong(i); Date date = new Date(aLong); return date; } //将数据库中类型 转换成java类型 public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException { long aLong = callableStatement.getLong(i); Date date = new Date(aLong); return date; } } 在MyBatis核心配置文件中进行注册 <!--注册类型处理器--> <typeHandlers> <typeHandler handler="com.itheima.handler.DateTypeHandler"></typeHandler> </typeHandlers> 测试转换是否正确测试代码片段:
UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setUsername("huicheng"); user.setPassword("123"); user.setBirthday(new Date()); mapper.save(user);测试结果:
MyBatis可以使用第三方的插件来对功能进行扩展,下面用分页助手PageHelper插件来演示,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据。
开发步骤:
导入通用PageHelper的坐标 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> <dependency> <groupId>com.github.jsqlparser</groupId> <artifactId>jsqlparser</artifactId> <version>0.9.1</version> </dependency> 在mybatis核心配置文件中配置PageHelper插件 <!-- 注意:分页助手的插件 配置在通用馆mapper之前 --> <plugin interceptor="com.github.pagehelper.PageHelper"> <!-- 指定方言 --> <property name="dialect" value="mysql"/> </plugin> 测试分页数据获取 //设置分页相关参数 当前页+每页显示的条数 PageHelper.startPage(1,2); List<User> userList = mapper.findAll(); for(User user:userList){ System.out.println(user); }测试结果:
获得分页相关的其他参数:
//获得与分页相关参数 PageInfo<User> pageInfo = new PageInfo<User>(userList); System.out.println("当前页:"+pageInfo.getPageNum()); System.out.println("每页显示条数:"+pageInfo.getPageSize()); System.out.println("总条数:"+pageInfo.getTotal()); System.out.println("总页数:"+pageInfo.getPages()); System.out.println("上一页的页码数:"+pageInfo.getPrePage()); System.out.println("下一页的页码数:"+pageInfo.getNextPage()); System.out.println("是否是第一个:"+pageInfo.isIsFirstPage()); System.out.println("是否是最后一个:"+pageInfo.isIsLastPage());测试结果:
知识小结:
properties标签:该标签可以加载外部的properties文件typeAliases标签:设置类型别名environments标签:数据源环境配置标签typeHandlers标签:配置自定义类型处理器plugins标签:配置MyBatis的插件多表查询关键是让MyBatis知道把查询到的结果正确的封装到相应的对象中,这需要我们进行配置告知MyBatis。
一对一查询的模型: 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户。
对应的sql语句:SELECT *,o.id oid FROM USER u,orders o WHERE u.id=o.uid 查询的结果如下:
创建Order和User实体
配置OrderMapper.xml
<resultMap id="orderMap" type="order"> <!--手动指定字段与实体属性的映射关系 column: 数据表的字段名称 property:实体的属性名称 --> <id column="oid" property="id"></id> <result column="ordertime" property="ordertime"></result> <result column="total" property="total"></result> <!--<result column="uid" property="user.id"></result> <result column="username" property="user.username"></result> <result column="password" property="user.password"></result> <result column="birthday" property="user.birthday"></result>--> <!-- property: 当前实体(order)中的属性名称(private User user) javaType: 当前实体(order)中的属性的类型(User) --> <association property="user" javaType="user"> <id column="uid" property="id"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> </association> </resultMap> <select id="findAll" resultMap="orderMap"> SELECT *,o.id oid FROM orders o,USER u WHERE o.uid=u.id </select>测试代码:
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class); List<Order> orderList = mapper.findAll(); for(Order order:orderList){ System.out.println(order); }测试结果:
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单。
对应的sql语句:SELECT *,o.id oid FROM orders o,USER u WHERE o.uid=u.id 查询的结果如下:
修改User实体:
配置UserMapper.xml
<resultMap id="userMap" type="user"> <id column="uid" property="id"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> <!--配置集合信息 property:集合名称 ofType:当前集合中的数据类型 --> <collection property="orderList" ofType="order"> <!--封装order的数据--> <id column="oid" property="id"></id> <result column="ordertime" property="ordertime"></result> <result column="total" property="total"></result> </collection> </resultMap> <select id="findAll" resultMap="userMap"> SELECT *,o.id oid FROM USER u,orders o WHERE u.id=o.uid </select>测试代码:
UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.findAll(); for(User u: userList){ System.out.println(u); }测试结果:
17:07:21,807 DEBUG findAll:159 - <== Total: 3 User{id=1, username='zhangsan', password='123', birthday=null, orderList=[Order{id=1, ordertime=Thu Jan 01 08:00:02 CST 1970, total=3000.0, user=null}, Order{id=2, ordertime=Thu Jan 01 08:00:02 CST 1970, total=4000.0, user=null}], roleList=null} User{id=2, username='lisi', password='123', birthday=null, orderList=[Order{id=3, ordertime=Thu Jan 01 08:00:02 CST 1970, total=5000.0, user=null}], roleList=null}多对多查询的模型: 用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用多对多查询的需求:查询用户同时查询出该用户的所有角色。
对应的sql语句:
SELECT * FROM USER u,sys_user_role ur,sys_role r WHERE u.id=ur.userId AND ur.roleId=r.id查询的结果如下:
创建Role实体,修改User实体:
配置UserMapper.xml
<resultMap id="userRoleMap" type="user"> <!--user的信息--> <id column="userId" property="id"></id> <result column="username" property="username"></result> <result column="password" property="password"></result> <result column="birthday" property="birthday"></result> <!--user内部的roleList信息--> <collection property="roleList" ofType="role"> <id column="roleId" property="id"></id> <result column="roleName" property="roleName"></result> <result column="roleDesc" property="roleDesc"></result> </collection> </resultMap> <select id="findUserAndRoleAll" resultMap="userRoleMap"> SELECT * FROM USER u,sys_user_role ur,sys_role r WHERE u.id=ur.userId AND ur.roleId=r.id </select>测试代码:
UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> all = mapper.findUserAndRoleAll(); for(User u:all){ System.out.println(u); }测试结果:
知识小结:
一对一配置:使用<resultMap>做配置一对多配置:使用<resultMap>+<collection>做配置多对多配置:使用<resultMap>+<collection>做配置修改MyBatis的核心配置文件,我们使用了注解替代的映射文件,所以我们只需要加载使用了注解的Mapper接口即可
或者指定扫描包含映射关系的接口所在的包也可以。
实现复杂关系映射之前我们可以在映射文件中通过配置来实现,使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置
一对一查询的模型: 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
对应的sql语句:
select * from orders; select * from user where id=查询出订单的uid;使用注解配置Mapper
上面是通过两次查询得到的,也可以用一句SQL语句进行查询配置注解。 代码如下:
@Select("select *,o.id oid from orders o,user u where o.uid=u.id") @Results({ @Result(column = "oid",property = "id"), @Result(column = "ordertime",property = "ordertime"), @Result(column = "total",property = "total"), @Result(column = "uid",property = "user.id"), @Result(column = "username",property = "user.username"), @Result(column = "password",property = "user.password") }) public List<Order> findAll();一对多查询的模型: 用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单。
对应的sql语句:
select * from user; select * from orders where uid=查询出用户的id;使用注解配置Mapper
多对多查询的模型: 用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用多对多查询的需求:查询用户同时查询出该用户的所有角色。
spring整合MyBatis主要是将sessionFactory的创建权交给spring框架。不用我们再service层中进行创建。具体配置如下:
在applicationContext.xml文件中配置:
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!--组件扫描 扫描service和mapper--> <!--同时也扫描了resources下的映射文件--> <context:component-scan base-package="com.itheima"> <!--排除controller的扫描--> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"></context:exclude-filter> </context:component-scan> <!--加载propeties文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置数据源信息--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!--配置sessionFactory--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <!--加载mybatis核心文件--> <property name="configLocation" value="classpath:sqlMapConfig-spring.xml"></property> </bean> <!--扫描mapper所在的包 为mapper创建实现类--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.itheima.mapper"></property> </bean> <!--声明式事务控制--> <!--平台事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置事务增强--> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> <!--事务的aop织入--> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:advisor> </aop:config> </beans>sqlMapConfig-spring.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> <!--定义别名--> <typeAliases> <!--<typeAlias type="com.itheima.domain.Account" alias="account"></typeAlias>--> <package name="com.itheima.domain"></package> </typeAliases> </configuration>