MyBatis框架
1 - MyBatis框架介绍
什么是框架:
框架是一个框架,半成品的软件,定义好了一些基础功能,需要加入你的功能就是完整的,基础功能是可重复使用的,可升级的软件
框架的特点:
1.框架一般不是全能的,不能做所有事情
2.框架是针对某一个领域有效。特长在某一个方面,比如mybatis做数据库操作强,但是其他的不能做
3.框架是一个软件
使用Jdbc的缺陷:
1.代码比较多,开发效率低
2.需要关注connection,statement,result对象创建个销毁
3.重复的代码比较多些
4.对result查询的结果,需要自己封装为list
5.业务代码和数据库的操作混在一起
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
2 - MyBatis下载与安装
下载地址:https://github.com/mybatis/mybatis-3/releases
如上图,第一个为MyBatis框架压缩包,第二个和第三个分别为Windows何Linux系统下的源代码,如果只需使用下载第一个即可
最后解压文件夹
3 - MyBatis框架搭建
准备工作(创建表和基础数据):
1)创建项目,并且在创建文件夹:WEB-INF/lib 搭建包结构(WEB-INF/lib/相关jar包)
2)导入mybatis相关的jar包和mysql驱动包,导入log4j(D:\MyBatis\mybatis-3.5.5\lib\log4j-1.2.17.jar)相关jar包
3)在src根下创建mybatis注配置文件mybatis-config.xml和日志文件log4j.properties,搭建配置文件结构和日志文件(文档第3页)
src/mybatis-config.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="development"> <environment id="development"> <transactionManager type="JDBC"/> <!--事务管理--> <dataSource type="POOLED"> <!--数据源,连接池等--> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/springdb?useSSL=FALSE&serverTimezone=UTC&allowPublicKeyRetrieval=true"/> <property name="username" value="root"/> <property name="password" value="101323"/> </dataSource> </environment> <!-- <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> --> </environments> <mappers> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <mapper resource="com/howie/mapper/ProvinceMapper.xml"/> </mappers> </configuration>
src/log4j.properties 文件内容如下
# Global logging configuration log4j.rootLogger=DEBUG,stdout # MyBatis logging configuration ... log4j.logger.org.mybatis.example.BlogMapper=TRACE # 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
4)创建mapper包结构,创建SQL映射文件XxxMapper.xml(文档第4页),这里以province数据表为例
src/com/howie/mapper/ProvinceMapper.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:命名空间 不同mapper映射文件使用namespace来区分 不同的mapper映射文件所使用的namespace的命名不允许出现重复 使用命名空间.sqlId的形式来找到我们想要执行的sql语句。例如 province.getById --> <mapper namespace="province"> <!--<select id="getById"></select>--> <!-- sql语句必须写在相应的标签中,标签有update,delete,insert,select <insert>:在标签对中写insert开头的SQL语句,处理添加操作 <update>:在标签对中写update开头的SQL语句,处理更新操作 <delete>:在标签对中写delete开头的SQL语句,处理添删除作 <select>:在标签对中写select开头的SQL语句,处理查询操作 属性parameterType:为SQL语句传递的参数的类型 属性resultType:SQL语句执行完后返回的数据类型 --> <select id="getById" parameterType="java.lang.Integer" resultType="com.howie.domain.Province"> select * from province where id = #{id} </select> </mapper>
5)搭建测试包与测试类,测试根据id查单条,模板在文档第2页
src/com/howie/test/Test1.java
package com.howie.test; import com.howie.domain.Province; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; public class Test1 { public static void main(String[] args) { String resource = "mybatis-config.xml"; // 输出流 InputStream inputStream = null; try { // 通过加载MyBatis的主配置文件mybatis-config.xml,创建输入流对象 inputStream = Resources.getResourceAsStream(resource); } catch(Exception e){ e.printStackTrace(); } /* SqlSessionFactoryBuilder:SqlSessionFactory的建造者。通过该建造者对象调用建造方法,为我们创建一个SqlSessionFactory对象 sqlSessionFactory对象唯一的作用就是为我们创建SqlSession对象我们未来所有的操作,使用的都是SqlSession对象 */ SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); // Java与数据库之间的会话 // 我们未来所有的操作(例如:增删改查,处理事务等),使用的都是SqlSession对象 /* 需求:根据id查询单条数据 如果取得的是单条记录,我们调用selectOne方法 参数1:根据命名空间.sqlId的形式找到我们需要使用的SQL语句 参数2:我们要为SQL语句中传递的参数 */ Province province = session.selectOne("province.getById", 1); System.out.println(province); session.close(); } }
4 - MyBatis完成基本的增删改查操作
基于上面的搭建,我们可以在MyBatis中完成基础的操作,这里的操作都是在测试类中完成
src/com/howie/mapper/ProvinceMapper.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:命名空间 不同mapper映射文件使用namespace来区分 不同的mapper映射文件所使用的namespace的命名不允许出现重复 使用命名空间.sqlId的形式来找到我们想要执行的sql语句。例如 province.getById --> <mapper namespace="province"> <!--<select id="getById"></select>--> <!-- sql语句必须写在相应的标签中,标签有update,delete,insert,select <insert>:在标签对中写insert开头的SQL语句,处理添加操作 <update>:在标签对中写update开头的SQL语句,处理更新操作 <delete>:在标签对中写delete开头的SQL语句,处理添删除作 <select>:在标签对中写select开头的SQL语句,处理查询操作 属性parameterType:为SQL语句传递的参数的类型 属性resultType:SQL语句执行完后返回的数据类型 --> <!--1.根据id获取省份信息--> <select id="getById" parameterType="java.lang.Integer" resultType="com.howie.domain.Province"> select * from province where id = #{id} </select> <!--2.获取所有的身份信息,如果返回的是多条记录,那么resultType返回值类型,应该是集合的泛型--> <select id="getAllProvince" resultType="com.howie.domain.Province"> select * from province; </select> <!--3.添加省份信息--> <!-- 注意:在未来实际项目开发中,所有的标签都必须要写id属性 <select>标签,parameterType属性可以省略不写,resultType属性必须得写 对于 <inert> <update> <delete>这3个标签,通常只写id属性,其他属性一概不写 --> <insert id="insertProvince"> insert into province(id,name,jiancheng,shenghui) values(#{id},#{name},#{jiancheng},#{shenghui}); </insert> <!--4.根据id修改操作--> <update id="updateProvince"> update province set name = #{name},jiancheng = #{jiancheng},shenghui = #{shenghui} where id = #{id}; </update> <!--5.根据id删除操作--> <delete id="deleteProvince"> delete from province where id = #{id}; </delete> </mapper>
src/com/howie/test/Test1.java
package com.howie.test; import com.howie.domain.Province; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.InputStream; import java.util.List; public class Test1 { public static void main(String[] args) { String resource = "mybatis-config.xml"; // 输出流 InputStream inputStream = null; try { // 通过加载MyBatis的主配置文件mybatis-config.xml,创建输入流对象 inputStream = Resources.getResourceAsStream(resource); } catch(Exception e){ e.printStackTrace(); } /* SqlSessionFactoryBuilder:SqlSessionFactory的建造者。通过该建造者对象调用建造方法,为我们创建一个SqlSessionFactory对象 sqlSessionFactory对象唯一的作用就是为我们创建SqlSession对象我们未来所有的操作,使用的都是SqlSession对象 */ SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); // Java与数据库之间的会话 // 我们未来所有的操作(例如:增删改查,处理事务等),使用的都是SqlSession对象 /* 需求1:根据id查询单条数据 如果取得的是单条记录,我们调用selectOne方法 参数1:根据命名空间.sqlId的形式找到我们需要使用的SQL语句 参数2:我们要为SQL语句中传递的参数 Province province = session.selectOne("province.getById", 1); System.out.println(province); session.close(); */ // 需求2:查询省份信息表中所有省份信息 /* List<Province> provinceList = session.selectList("province.getAllProvince"); for(Province p:provinceList){ System.out.println(p); } session.close();*/ // 需求3:添加操作 /* 注意:MyBatis默认情况下是手动提交事务(session.commit(true)); */ /* Province province = new Province(10,"云南","滇","昆明"); int updateColumnCount = session.insert("province.insertProvince", province); session.commit(false); if(updateColumnCount > 0){ System.out.println("添加成功!"); } else{ System.out.println("添加失败!"); } session.close();*/ // 需求4:修改操作 /* Province province = new Province(10,"云南","云","昆明"); int updateColumnCount = session.update("province.updateProvince", province); session.commit(false); if(updateColumnCount > 0){ System.out.println("修改成功!"); } else{ System.out.println("修改失败!"); } session.close();*/ // 需求5:根据id删除 int updateCount = session.delete("province.deleteProvince",10); session.commit(false); if(updateCount > 0){ System.out.println("删除成功!"); } else{ System.out.println("删除失败!"); } session.close(); } }
5 - MyBatis解决JDBC存在的问题
1)获取连接、得到Statement、处理ResultSet、关闭资源非常繁琐,而在MyBatis中使用SqlSesstion搞定一切
2)将sql语句写死到java代码中,如果修改SQL语句,必须修改java代码,必须重新编译,程序可维护性不高,而在MyBatis中,将SQL语句配置在Mapper.xml文件中与java代码分离
3)向PreparedStatement 对占位符的位置设置参数时,非常繁琐。而Mybatis自动将java对象映射至SQL语句,通过statement中的 parameterType定义输入参数的类型
4)解析结果集时需要把字段的值设置到相应的实体类属性名中。而Mabatis自动将SQL执行结果映射至java对象,通过statement中的resultType定义输出结果的类型
4 - MyBatis结合Dao层的开发
1.MyBatis结合Dao层的操作
项目结构图
<1>dao层
dao/ProvinceDao.java
package com.howie.dao; import com.howie.domain.Province; public interface ProvinceDao { public Province getProvinceById(String id); public void insertProvince(Province province); }
dao/ProvinceDaoImpl.java
package com.howie.dao; import com.howie.domain.Province; import com.howie.util.SqlSessionUtil; import org.apache.ibatis.session.SqlSession; public class ProvinceDaoImpl implements ProvinceDao { @Override public Province getProvinceById(String id) { SqlSession session = SqlSessionUtil.getSession(); return session.selectOne("province.getById",id); } @Override public void insertProvince(Province province) { SqlSession session = SqlSessionUtil.getSession(); session.insert("province.save",province); } }
<2>domain层(存放JavaBean略)
<3>mapper层
mapper/ProvinceMapper.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="province"> <!--1.根据id,获取省份信息--> <select id="getById" parameterType="java.lang.String" resultType="com.howie.domain.Province"> select * from province where id = #{id}; </select> <!--2.添加省份信息--> <insert id="save"> insert into province(id,name,jiancheng,shenghui) values(#{id},#{name},#{jiancheng},#{shenghui}); </insert> </mapper>
<4>service层
service/ProvinceService.java
package com.howie.service; import com.howie.domain.Province; public interface ProvinceService { public Province getProvinceById(String id); public void insertProvince(Province province); }
service/impl/ProvinceServiceImpl.java
package com.howie.service.impl; import com.howie.dao.ProvinceDao; import com.howie.dao.ProvinceDaoImpl; import com.howie.domain.Province; import com.howie.service.ProvinceService; public class ProvinceServiceImpl implements ProvinceService { // 接口 = 接口实现类 private ProvinceDao provinceDao = new ProvinceDaoImpl(); // 多态 @Override public Province getProvinceById(String id) { return provinceDao.getProvinceById(id); } @Override public void insertProvince(Province province) { provinceDao.insertProvince(province); } }
<5>test层
test/Test1.java
package com.howie.test; import com.howie.domain.Province; import com.howie.service.ProvinceService; import com.howie.service.impl.ProvinceServiceImpl; import com.howie.util.ServiceFactory; import com.howie.util.TransactionInvocationHandler; public class Test1 { public static void main(String[] args) { // 这里得到代理类(经济人) ProvinceService provinceService = (ProvinceService) ServiceFactory.getService(new ProvinceServiceImpl()); // 测试业务1 /*Province province = provinceService.getProvinceById("1"); System.out.println(province);*/ // 测试业务2 Province province = new Province(10,"云南","滇","昆明"); provinceService.insertProvince(province); } }
<6>util层
util/ServiceFactory.java
package com.howie.util; public class ServiceFactory { // 传递被代理对象(明星),得到代理对象(经济人) public static Object getService(Object service){ return new TransactionInvocationHandler(service).getProxy(); } }
util/SqlSessionUtil.java
package com.howie.util; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class SqlSessionUtil { private static SqlSessionFactory sqlSessionFactory; // 静态代码块的特点:随着类的加载而加载,并且只执行一次 static { String resource = "mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); } catch (IOException e) { e.printStackTrace(); } sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } private SqlSessionUtil(){} // 单例模式 // 设置共享变量,供每个线程使用(保证session唯一) private static ThreadLocal<SqlSession> t = new ThreadLocal<SqlSession>(); // 获取SqlSession对象 public static SqlSession getSession(){ SqlSession session = t.get(); // get() 方法:获取与当前线程关联的ThreadLocal值 if(session == null){ session = sqlSessionFactory.openSession(); t.set(session); } return session; } // 关闭SqlSession对象 public static void closeSession(SqlSession session){ if(session != null){ session.close(); t.remove(); // remove() 将与当前线程关联的ThreadLocal值删除 /* 此句代码必须得加,非常重要,因为服务器分配的线程执行完毕后并没有被销毁,而是回到线程池(Tomcat服务器自带)中 必须强制释放共享变量,才能保证只有一个session,以免保证SQL语句执行不紊乱 */ } } }
util/TransactionInvocationHandler.java
package com.howie.util; import org.apache.ibatis.session.SqlSession; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class TransactionInvocationHandler implements InvocationHandler { private Object target; // 目标对象(明星)即被代理类 public TransactionInvocationHandler(Object target) { this.target = target; } // 代理类的业务方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession session = null; Object obj = null; try{ session = SqlSessionUtil.getSession(); // 处理业务逻辑 obj = method.invoke(target,args); // 被代理类的方法 // 处理业务逻辑完毕后,提交事务 session.commit(); } catch(Exception e){ if(session != null){ session.rollback(); } e.printStackTrace(); } finally{ SqlSessionUtil.closeSession(session); } return obj; } // 取得代理类对象(经济人),只能由此方法取得 public Object getProxy(){ return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } }
2.MaBatis对dao层动态代理的支持
1)我们以前用过动态代理,我们是在业务层(service)使用的,在业务层使用动态代理是为了实现事务管理,业务层的动态代理是我们自己手写的,业务层之所以要使用动态代理,是因为业务层本身就是用来处理业务逻辑的
2)事务相关的代码不方便放在业务层处理。所以我们想到使用代理类帮助业务层去处理。现在我们要在dao层也要加入动态代理,dao 层之所以创建代理类,是因为写 dao层实现类本身就是一种不方便。
3)在结合了MyBatis的动态代理机制后,以后的实际项目开发,dao层的 impl就不写了。MyBatis的动态代理不用我们自己手写,在MyBatis中已经集成好的一种机制,我们直接拿来使用就可以了。
基于以上代码的改进(最终结构图)
为dao层加入动态代理
dao层
dao/ProvinceDao.java
package com.howie.dao; import com.howie.domain.Province; import java.util.List; public interface ProvinceDao { /* 注意这里的方法名:必须和标签的id属性相同 */ public Province getById(String id); public void save(Province province); List<Province> getAll(); }
dao/ProvinceDao.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="com.howie.dao.ProvinceDao"> <!--1.根据id,获取省份信息--> <select id="getById" parameterType="java.lang.String" resultType="com.howie.domain.Province"> select * from province where id = #{id}; </select> <!--2.添加省份信息--> <insert id="save"> insert into province(id,name,jiancheng,shenghui) values(#{id},#{name},#{jiancheng},#{shenghui}); </insert> <!--3.查询所有省份信息--> <select id="getAll" resultType="com.howie.domain.Province"> select * from province; </select> </mapper>
service层
service/impl/ProvinceServiceImpl.java
package com.howie.service.impl; import com.howie.dao.ProvinceDao; import com.howie.domain.Province; import com.howie.service.ProvinceService; import com.howie.util.SqlSessionUtil; import java.util.List; public class ProvinceServiceImpl implements ProvinceService { private ProvinceDao provinceDao = SqlSessionUtil.getSession().getMapper(ProvinceDao.class); // 帮我们创建实现类 @Override public Province getById(String id) { return provinceDao.getById(id); } @Override public void save(Province province) { provinceDao.save(province); } @Override public List<Province> getAll() { return provinceDao.getAll(); } }
service/ProvinceService.java
package com.howie.service; import com.howie.domain.Province; import java.util.List; public interface ProvinceService { public Province getById(String id); public void save(Province province); List<Province> getAll(); }
MyBatis的动态代理:
List<City> selectCities(); // Dao层接口里的抽象方法
1.dao对象,类型是CityDao,权限名称是:com.howie.dao.CityDao。权限名称和namespace是一样的
2.方法名称,selectCities,这个方法就是mapper文件中的id值 selectCities
3.通过dao中方法的返回值也可以确定mybatis要调用的SqlSession的方法,
如果返回值是list,调用的是SqlSession.selectList()方法
如果返回值 int,或者是非list的,看mapper文件中 的标签insert update 就会调用SqlSession的insert,update等方法
mybatis的动态代理:mybatis根据dao的方法调用,获取执行sql语句的信息
mybatis根据你的接口,创建出一个dao接口的实现类,并创建这个类的对象。完成SqlSession调用方法,访问数据库。
使用mybatis的动态代理机制:
使用SqlSession.getMapper(dao接口.class),getMapper能获取dao接口对于的实现类对象
5 - MyBatis配置文件
1.properties 把连接与加载数据需要的信息封装到src/db.properties
src/db.properties
# 据库连接需要的4个基本信息 username=root password=101323 url=jdbc:mysql://localhost:3306/springdb?useSSL=FALSE&serverTimezone=UTC&allowPublicKeyRetrieval=true # jdbc:mysql://localhost:3306/springdb?useSSL=FALSE&serverTimezone=UTC&allowPublicKeyRetrieval=true driver=com.mysql.cj.jdbc.Driver
src/mybatis-config.xml 设置从db.properties中读取数据
<?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="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <mapper resource="com/howie/dao/ProvinceDao.xml"/> </mappers> </configuration>
2.settings 在主配置文件中(mybatis-config.xml)
<settings> <!-- 设置数据库交互的环境,例如可以在此处配置二级缓存,配置查询延迟加载策略等等 配置的目的是为了更加有效的查询表中的记录 在实际项目开发中,settings的设置基本没有用,因为settings对于查询的优化,得到的效果不明显 对于海量级别的数据,使用settings配置优化,起不到任何的效果 对于数据量较少的项目,对于查询的效率要求比较低,也没有必要使用settings配置 如果遇到了海量级别的数据,我们如何去提高查询的效率? 基础操作 对于常用的查询条件的字段(如姓名),设置索引 高级操作 使用nosql数据库,redis 专业操作 针对于电商行业 搜索引擎选择:Elasticsearch 与 solr --> <setting name="" value=""/> </settings>
3.为mapper映射文件的domain起别名(mybatis-config.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"/> <!--加载文件内容--> <!--为mapper映射文件的domain器别名--> <typeAliases> <!-- 方式1:为指定的类分别起别名,别名的命名由我们来决定 <typeAlias type="com.howie.domain.Province" alias="pd"/> type:要为那个domain起别名,填写包.类名称 alias:别名的名字 --> <!-- 方式2:使用package标签批量起别名 别名是MyBatis默认为我们取好的,命名不是由我们自己决定,别名为类(类名的字母不区分大小写) name:指定一个包结构,表示在该包下,所有的domain自动起好了别名 --> <package name="com.howie.domain"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!--事务管理--> <dataSource type="POOLED"> <!--数据源,连接池等--> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <mapper resource="com/howie/dao/ProvinceDao.xml"/> </mappers> </configuration>
总结:
1)未来实际项目开发中,如果公司需要使用起别名的机制,我们要使用批量起别名的方式
2)在市场上也有很多企业摒弃使用MyBatis起别名的机制,公司会认为将domain写成全路径,可以有效的提高代码的可读性
4.Mapper映射文件注册(mybatis-config.xml)
<mappers> <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>--> <!--Mapper映射文件注册--> <!--方式1:使用resource属性--> <mapper resource="com/howie/dao/ProvinceDao.xml"/> <!--方式2:使用class属性,找到dao层接口的全路径--> <!--<mapper class="com.howie.dao.ProvinceDao"/>--> <!--方式3:批量注册。name属性:指向dao层的包,表示在该dao包下,所有的mapper映射文件自动注册,推荐使用--> <!--<package name="com.howie.dao"/>--> </mappers>
6 - MyBatis映射文件
1.parameterType
设置参数类型:parameterType用于设置输出参数的Java类型,parameterType的值为参数类型的java类型或者别名,SQL语句获取参数的值使用#{}或者${},使用时可以省略
1)使用基本数据类型为参数
2)使用引用数据类型为参数
3)使用map为参数
package com.howie.test; import com.howie.dao.ProvinceDao; import com.howie.domain.Province; import com.howie.util.SqlSessionUtil; import java.util.HashMap; import java.util.Map; public class Test2 { public static void main(String[] args) { // 接口 = 接口实现类 ProvinceDao provinceDao = SqlSessionUtil.getSession().getMapper(ProvinceDao.class); // 创建实现类 // 1.测试parameterType以基本数据类型或String为参数 /*Province province = provinceDao.getById("10"); System.out.println(province);*/ /* 1.结论对于常用的数据类型系统已为我们起好了别名。例如:java.lang.String -- > String/string等,此时参数类型写别名即可 <select id="getById" parameterType="String" resultType="province"> 2.写SQL语句时,使用8种基本数据类型+String为参数时,#{}中的标识符可以随意去写,如 select * from student where id = #{id123} 但是虽然可以随意些,还是写的要见名知意 */ // 2.测试parameterType以引用数据类型为参数。需求:查询简称为滇,且id=10的省份信息(此时SQL语句有两个参数,但是调用查询语句时绝对不能同时传递两个参数) // 如果我们要为SQL语句传递多个参数,我们应该将这多个参数封装到一个domain对象中,或者是打包到一个map集合中 /*Province province = new Province(); province.setId(10); province.setJiancheng("滇"); System.out.println(provinceDao.select1(province));*/ /* 注意点:如果我们为SQL语句传递的参数类型为一个domain引用类型,那么#{}中的标识符必须是domain类的属性名 <select id="select1" parameterType="Province" resultType="Province"> select * from province where id = #{id} and jiancheng = #{jiancheng}; </select> */ // 3.测试parameterType以map集合为参数。需求:查询名称为云南,且id=10的省份信息 Map<String,Object> map = new HashMap<>(); map.put("name","云南"); map.put("id",10); Province province = provinceDao.select2(map); System.out.println(province); /* 注意点:如果我们为SQL语句传递的参数类型为一个domain引用类型,那么#{}中的标识符必须是Map的key */ /* 总结:在实际项目开发过程中,使用domain引用类型,或者使用map集合类型都可以为SQL语句同时传递多个参数 一般情况下,我们使用domain就可以了 当domain不符合需求情况下,我们一定要考虑使用map来传值 */ } }
2.#{}与${}
使用在SQL语句中的符号
1)#{}:表示占位符,可以有效防止SQL注入。使用#{}设置参数。无序考虑参数的类型 PreparedStatement
2)${}:表示拼接符,无法防止SQL注入,使用${}设置参数必须考虑参数的类型 Statement
3)传递简单类型参数
a.如果获取简单类型参数,#{}中可以使用value或其他名称
b.如果获取简单类型参数,${}中只能使用valeu。例如:select * from tbl_student where id='${value}'
4)在没有特殊要求的情况下,通常使用#{}占位符
5)有些情况必须使用${},比如需要动态拼接表名,select * from ${tablename} 比如:动态拼接排序字段:select * from tablename order by ${username} desc
6)重点案例:
使用${}执行 like 模糊查询
使用#{} 执行like 模糊查询
package com.howie.test; import com.howie.dao.ProvinceDao; import com.howie.domain.Province; import com.howie.util.SqlSessionUtil; import java.util.List; public class Test2 { public static void main(String[] args) { // 接口 = 接口实现类 ProvinceDao provinceDao = SqlSessionUtil.getSession().getMapper(ProvinceDao.class); // 创建实现类 // 测试:like 模糊查询。方式1:${} --- 了解即可 // 案例:查询省份名称带"江"的信息 /*List<Province> provinceList = provinceDao.select3("江"); for(Province p:provinceList){ System.out.println(p); }*/ // 测试:like 模糊查询。方式2:#{} 了解即可 // 案例:查询省份名称带"江"的信息(注意这里传参时只能是以 "%参数%" 的形式传给SQL语句,但是这种传值方式显然是不合理的) /*List<Province> provinceList = provinceDao.select4("%江%"); for(Province p:provinceList){ System.out.println(p); }*/ // 测试:like 模糊查询。方式3:#{} // 案例:查询省份名称带"江"的信息 /* select * from province where name like '%' #{name} '%'; MySQL中空格相当于+号(拼接) */ List<Province> provinceList = provinceDao.select5("江"); for(Province p:provinceList){ System.out.println(p); } } }
3.resultType
设置返回值类型
1)返回基本数据类型+String
2)返回domain
3)返回hashMap(注意返回值类型)
package com.howie.test; import com.howie.dao.ProvinceDao; import com.howie.domain.Province; import com.howie.util.SqlSessionUtil; import java.util.List; import java.util.Map; import java.util.Set; public class Test2 { public static void main(String[] args) { // 接口 = 接口实现类 ProvinceDao provinceDao = SqlSessionUtil.getSession().getMapper(ProvinceDao.class); // 创建实现类 // 测试:resultType 返回基本数据类型 // 案例1:查询出id为10的省份名称 /*String name = provinceDao.select6("10"); System.out.println("省份名称:" + name);*/ // 案例2:查询所有省份名称。返回 String /*List<String> nameList = provinceDao.select7(); for(String name:nameList){ System.out.println(name); }*/ // 案例3:查询表中的省份数量,返回 int /*int count = provinceDao.select8(); System.out.println(count);*/ // 测试:resultType, 返回domain类型。略 // 测试:resultType, 返回map类型。 List<Map<String,Object>> mapList = provinceDao.select9(); for(Map<String,Object> map:mapList){ Set<String> keySet = map.keySet(); for(String key:keySet){ System.out.println("key:" + key); System.out.println("value:" + map.get(key)); } System.out.println("-------------"); } /* 说明:对于SQL语句查询的结果,我们使用domain来封装这些结果很方便,为什么还要使用map呢? 因为对于查询结果,很多情况,使用domain封装不了,所以我们会想到使用map来保存结果 例如: 根据省份名称分组,查询出来每一个姓名对应的数量 select # 当使用group by 分组后select 后面只能使用聚合函数或者分组字段 name,count(*) from province group by name; 以上查询结果,不适合用domain进行封装(没有count(*)属性),适合使用map封装查询结果 */ } }
查询结果封装过程解析: <select id="" resultType="Student"> select * from tbl_student; </select> 当执行了SQL语句之后,通过查询得到的结果 id,name,age。根据返回值类型,会自动为我们创建出来一个该类型的对象,由该对象将查询的结果封装起来 Student s1 = new Student(); s1.setId(id); s1.setName(name); s1.setAge(age); 当查询出来了第二条记录,根据返回值类型,再一次创建出来一个对象,封装第二条记录的值 Student s2 = new Student(); s2.setId(id); s2.setName(name); s2.setAge(age); ... ... 多条记录封装成了多个Student对象,系统会自动的为我们创建出来一个List集合来保存这些对象 List<Student>list = ArrayList<>(); list.add(s1); list.add(s2); ... ... <select id="" resultType="hashMap"> select * from tbl_student; </select> 当执行了SQL语句之后,通过查询得到的结果 id,name,age。根据返回值类型没会自动为我们创建出来一个该类型的对象(map对象),由该对象将查询的结果保存起来 Map<String,Object> map1 = HashMap<String,Object>(); map1.put("id",id); map1.put("name",name); map1.put("age",age); 当查询出来第二条记录,根据返回值类型,再一次创建出来一个对象(map对象),保存第二条记录的值 Map<String,Object> map2 = HashMap<String,Object>(); map2.put("id",id); map2.put("name",name); map2.put("age",age); ... ... 多条记录封装为多个map对象 系统会自动的为我们创建出来一个List集合来保存这些map对象 List<Map<String,Object>> mapList = new ArrayList<>(); mapList.add(map1); mapList.add(map2); ... ...
4)当查询字段名和domain属性名不一致时解决的方案
a.为字段起别名,别名为类中属性名
select id,fullname as name,age from tbl_student
b.使用sesultMap
id:resultMap标签对是唯一标识
type:指定一个类型,与数据表一一对应,建立表字段和类属性的名字一一匹配的关系
将来在使用到该resultMap标签的时候,使用id来找到这组标签
<resultMap type="Student" id="stuMap">
<id property="id" column="id"/> <!--id标签:用来配置主键的对应关系的-->
<result property="name" column="fullname"/> <!--result标签:用来配置普通字段对应关系-->
<result property="age" column="age"/>
<!--
property属性:配置的是类中的属性名
column属性:配置的表中的字段名
这样就能够建立起类属性和表字段一一对应的关系了
-->
</resultMap>
<select id="selectStudent" resultMap="stuMap">
select * from tbl_student
</select>
7 - MyBatis动态SQL
1.用动态SQL
1)在实际开发过程中,往往会遇到各种各样的需求,我们不可能为每一个需求都创建SQL语句,肯定是要将SQL语句写成动态的形式。
2)动态SQL语句的核心思想就是,有那个查询条件,就动态的在 where 关键字后面挂在那个查询条件
2.代码示例
src/com/howie/test/Test2.java
package com.howie.test; import com.howie.dao.ProvinceDao; import com.howie.domain.Province; import com.howie.util.SqlSessionUtil; import java.util.List; import java.util.Map; import java.util.Set; public class Test2 { public static void main(String[] args) { // 接口 = 接口实现类 ProvinceDao provinceDao = SqlSessionUtil.getSession().getMapper(ProvinceDao.class); // 创建实现类 // 测试1:动态SQL where标签+if标签 /*Province province = new Province(); province.setShenghui("南"); // province.setJiancheng("昆明"); province.setName("江"); List<Province> provinceList = provinceDao.select1(province); for(Province p:provinceList){ System.out.println(p); }*/ // 测试2:动态SQL foreach标签 // 查询id为1,2,3,4,5,6的学员信息 /*String[] arrayId = new String[]{"1","2","10"}; List<Province> provinceList = provinceDao.select2(arrayId); for(Province p:provinceList){ System.out.println(p); }*/ // 测试3:SQL片段 Province province = provinceDao.select3(10); System.out.println(province); } }
src/com/howie/dao/ProvinceDao.xml
<!--测试--> <select id="select1" parameterType="Province" resultType="Province"> select * from province <!--where标签--> <!-- 当where标签在使用的时候,必须要搭配where标签对中的if标签来使用 通过if标签的判断,如果有查询条件,则展现where关键字,如果没有查询条件则不展现where关键字 where标签会自动的屏蔽掉第一个连接符 and/or --> <where> <if test="name!=null and name!=''"> name like '%' #{name} '%'; </if> <if test="jiancheng!=null and jiancheng!=''"> and jianchegn like '%' #{jiancheng} '%'; </if> </where> </select> <select id="select2" resultType="Province"> select * from province where id in <!-- foreach标签:用来遍历传递来的数组参数 collection:标识传递参数的类型。array->数组 list->集合 item:每一次遍历出来的元素,在使用该元素的时候,需要套用在#{}中 open:拼接循环的开始符号 close:拼接循环的结束符号 separator:元素与元素之间的分隔符 --> <!--例如:select * from province where id in(1,2,3,4); 可以写成如下代码--> <foreach collection="array" item="id" open="(" close=")" separator=","> #{id} </foreach> </select> <!-- 使用SQL标签制作SQL片段 SQL片段的作用是用来代替SQL语句中的代码 如果你的mapper映射文件中的SQL语句某些代码出现了大量的重复,我们可以使用SQL片段来代替他们 id属性:SQL片段的唯一标识,将来找到SQL片段使用id来进行定位 将来的实际项目开发中,使用SQL片段用来代替重复率高,且复杂的子查询 --> <sql id="sql1"> select * from province </sql> <select id="select3" parameterType="int" resultType="Province"> <include refid="sql1"/> where id = #{id}; </select>
8 - 多表查询案例
src/com/howie/test/Test2.java
package com.howie.test; import com.howie.dao.ProvinceDao; import com.howie.domain.Province; import com.howie.util.SqlSessionUtil; import com.howie.vo.ProvinceAndCityVo; import java.util.List; import java.util.Map; import java.util.Set; public class Test2 { public static void main(String[] args) { // 接口 = 接口实现类 ProvinceDao provinceDao = SqlSessionUtil.getSession().getMapper(ProvinceDao.class); // 创建实现类 // 测试多表查询 // 案例1:查询省份名称和省份划分的城市 /*List<Map<String,Object>> mapList = provinceDao.select1(); for(Map<String,Object> map:mapList){ Set<String> set = map.keySet(); for(String key:set){ System.out.println("省份:" + key); System.out.println("城市:" + map.get(key)); } }*/ // 案例2:查询所有省份和城市信息,加vo(view object)/(value object) 视图对象 /* 在实际项目开发中,如果需要为前端展现的数据,使用一个domain类型不足以表现出来这些数据 这时我们可以考虑使用两种技术来实现,分别为 使用map以及使用vo 例如我们现在的需求(查询省份和城市所有信息) 得到的结果,使用学生的domain或者班级的domain都不能够封装这些结果 所以我们可以使用map去保存这些信息 同时我们也可以使用vo类来保存这些信息,此时ov类需要我们自己创建出来,其属性由查询结果来决定 */ /*List<ProvinceAndCityVo> provinceAndCityList = provinceDao.select2(); for(ProvinceAndCityVo pc:provinceAndCityList){ System.out.println(pc); }*/ // 案例3:查询省份名称带江字的省份信息和城市信息 List<ProvinceAndCityVo> provinceAndCityVoList = provinceDao.select3("江"); for(ProvinceAndCityVo vo:provinceAndCityVoList){ System.out.println(vo); } /* 总结: 实际项目开发中,如果要为前端同时提供多组值,那么我们应该使用map还是vo呢? 如果前端的需求的重复率不高,那么我们选择临时使用map就可以了 如果前端对于需求的重复率较高,那么我们可以创建一个vo类来使用,非常方便 */ } }
mapper映射文件
<select id="select1" resultType="Map"> select p.name n,c.name from province p join city c where p.id = c.provinceid; </select> <select id="select2" resultType="com.howie.vo.ProvinceAndCityVo"> select p.id pid,p.name pname,p.jiancheng,p.shenghui,c.id cid,c.name cname from province p join city c where p.id = c.provinceid; </select> <select id="select3" parameterType="String" resultType="ProvinceAndCityVo"> select p.id pid,p.name pname,p.jiancheng,p.shenghui,c.id cid,c.name cname from province p join city c where p.id = c.provinceid and p.name like '%' #{name} '%'; </select>