Mybatis

一、mybatis的使用

  1. 使用mybatis需要添加mybatis的依赖,同时由于mybatis在是对jdbc的封装,也需要引入对应数据库的jdbc依赖
  2. 配置mybatis,在resources目录下创建一个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指定使用哪个环境下的配置(一般是dev,开发环境或者prd,生产环境)-->
  <environments default="development">
    <environment id="development">
  	  <!--使用JDBC进行事务管理-->
      <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"/>
  </mappers>
</configuration>
  1. 创建SqlSession,mybatis对于数据库的操作需要依赖于SqlSession对象。
		SqlSession session = null;
        try {
        	/*加载配置文件并且创建SqlSessionFactory*/
            Reader reader = Resources.getResourceAsReader("mybatis-conf.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
            /*开启session*/
            session = factory.openSession();
            Connection connection = session.getConnection();
            System.out.println(connection);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(session != null)
                session.close();
        }

二、执行crud

mybatis是对JDBC的封装,在JDBC中SQL语句与代码混在一起,mybatis实现了业务逻辑代码与SQL语句的解耦。所有的SQL语句都写在xml文件中,session对象负责执行这些SQL语句并且进行封装。在resources目录下新建mappers文件夹,在里面新建xml文件用来保存SQL语句。可以从官网上粘贴对应的dtd,所有的SQL语句都书写在mapper标签内,其中namespace指定当前xml文件的命名空间,需要注意的是这些语句是根据namespace和namespace里面语句的id来区分的。

  1. 查询
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goods">
	<!--查询语句,id用来标记当前的查询语句,resultType指定返回值得类型,如果指定的是Map会使用键值对的方式包装查询结果,键是表中的列名,值是查询结果。这与JDBC中的查询语句非常类似。默认使用的HashMap,如果需要保持查询顺序可以使用LinkedHashMap-->
	<select id="returnMap" resultType="java.util.Map">
        select * from t_goods
    </select>
</mapper>

执行查询语句

@Test
public void testReturnMap(){
	SqlSession session = null;
	try {
		/*使用自己编写的工具类打开SqlSession*/
		session = MyBatisUtil.openSession();
		/*由于查询的结果是多条数据所以使用selectList,goods是查询语句的命名空间,returnMap是查询语句的id*/
		List<Map> goods = session.selectList("goods.returnMap");
		for(Map map : goods)
			System.out.println(map);
	}catch (Exception e){
		e.printStackTrace();
	}finally {
		if(session != null)
		session.close();
	}
}

对于查询语句的返回结果,mybatis可以直接将结果包装成的实体对象,这个实体对象中不应该包含自定义的对象。查询语句可能输入参数,可以只传入一个参数,也可以使用Map对象
实体类:

package com.zyf.mybatis.Entity;

import java.io.Serializable;

public class Goods  implements Serializable {
    private Integer goodsId;
    private String title;
    private String subTitle;
    private Float originalCost;
    private Float currentPrice;
    private Float discount;
    private Integer isFreeDelivery;
    private Integer categoryID;

    public Goods() {
    }

    public Goods(String title, String subTitle, Float originalCost, Float currentPrice, Float discount, Integer isFreeDelivery, Integer categoryID) {
        this.title = title;
        this.subTitle = subTitle;
        this.originalCost = originalCost;
        this.currentPrice = currentPrice;
        this.discount = discount;
        this.isFreeDelivery = isFreeDelivery;
        this.categoryID = categoryID;
    }

    public Integer getGoodsId() {
        return goodsId;
    }

    public String getTitle() {
        return title;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public Float getOriginalCost() {
        return originalCost;
    }

    public Float getCurrentPrice() {
        return currentPrice;
    }

    public Float getDiscount() {
        return discount;
    }

    public Integer getIsFreeDelivery() {
        return isFreeDelivery;
    }

    public Integer getCategoryID() {
        return categoryID;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public void setOriginalCost(Float originalCost) {
        this.originalCost = originalCost;
    }

    public void setCurrentPrice(Float currentPrice) {
        this.currentPrice = currentPrice;
    }

    public void setDiscount(Float discount) {
        this.discount = discount;
    }

    public void setIsFreeDelivery(Integer isFreeDelivery) {
        this.isFreeDelivery = isFreeDelivery;
    }

    public void setCategoryID(Integer categoryID) {
        this.categoryID = categoryID;
    }

    @Override
    public String toString() {
        return this.getGoodsId()+"-"+this.getTitle()+"-"+this.getSubTitle()+"-"+this.getOriginalCost()+
                "-"+this.getCurrentPrice()+"-"+this.getDiscount()+"-"+this.getIsFreeDelivery()+"-"+
                this.getCategoryID();
    }
}

查询语句:

<!--parameterType指定输入数据的类型,如果只有一个输入数据直接使用#{value}获取对应的值,这里#{}的作用相当于JDBC中的PreparedStatement中的set*方法,而${}则相当于字符串拼接。resultType使用的是实体类,mybatis会自动对数据进行封装-->
<select id="selectById" parameterType="Integer" resultType="com.zyf.mybatis.Entity.Goods">
	select * from t_goods where goods_id = #{value}
</select>
<!--需要传入多个值时使用parameterType="java.util.Map",#{key}获取Map中的键对应的值-->
<select id="selectByPrice" parameterType="java.util.Map" resultType="com.zyf.mybatis.Entity.Goods">
	select * from t_goods where current_price between #{min} and #{max}
</select>

执行查询语句:

@Test
public void testSelectById(){
	SqlSession session = null;
	try {
		session = MyBatisUtil.openSession();
		Goods goods = session.selectOne("goods.selectById", 5739);
		System.out.println(goods);
	}catch (Exception e){
		e.printStackTrace();
	}finally {
		if(session != null)
		session.close();
	}
}
@Test
public void testSelectByPrice(){
	SqlSession session = null;
	try {
		session = MyBatisUtil.openSession();
		Map map = new HashMap();
		map.put("min" , 500);
		map.put("max", 1000);
		List<Goods> goods = session.selectList("goods.selectByPrice", map);
		System.out.println(goods);
	}catch (Exception e){
		e.printStackTrace();
	}finally {
		if(session != null)
			session.close();
	}
}

对于简单的对象(属性中不包含自定义对象),mybatis可以进行自定义的封装,对于复杂的对象,需要通过resultMap来指定表的列和对象的映射规则。
实体对象:

package com.zyf.mybatis.dto;
import com.zyf.mybatis.Entity.Goods;
public class GoodsDTO {
    private Goods goods;
    private String categoryName;
    public Goods getGoods() {
        return goods;
    }
    public void setGoods(Goods goods) {
        this.goods = goods;
    }
    public String getCategoryName() {
        return categoryName;
    }
    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }
    @Override
    public String toString() {
        return "GoodsDTO{" +
                "goods=" + goods +
                ", categoryName='" + categoryName + '\'' +
                '}';
    }
}

xml文件:

<!--对查询结果进行映射,必须要为这个映射对象指定主键属性,property指定对象的字段,如果内部对象的字段则需要使用对象名.字段名指定,column指定数据库的中的列名-->
<resultMap id="resultMap" type="com.zyf.mybatis.dto.GoodsDTO">
	<id property="goods.goodsId" column="goods_id" />
	<result property="goods.title" column="title" />
	<result property="goods.subTitle" column="sub_title" />
	<result property="goods.originalCost" column="original_cost" />
	<result property="goods.currentPrice" column="current_price" />
	<result property="goods.discount" column="discount" />
	<result property="goods.isFreeDelivery" column="is_free_delivery" />
	<result property="goods.categoryID" column="category_id" />
	<result property="categoryName" column="category_name" />
</resultMap>
<select id="selectGoodsDTO" resultMap="resultMap">
	select g.*, t.category_name
	from t_goods g, t_category t
	where g.category_id = t.category_id
</select>

执行查询语句的方式与前面相同。

  1. 插入
    插入语句一般传入实体对象,然而参入实体对象时我们一般不设置主键,而是使用SQL语句或者使用自增主键来产生。我们可以将生成的主键回填到体对象。对于支持自增主键的数据库,我们可以使用useGeneratedKeys,对于不支持自增主键的数据库我们需要使用selectKey 来产生主键并将值放入到数据库中。
<!--不支持的情况-->
<insert id="insert" parameterType="com.zyf.mybatis.Entity.Goods">
	<!--将产生的键放入到数据库中(对应keyColumn),并且回填到实体对象中(对应keyProperty)-->
	<selectKey keyProperty="goodsId" keyColumn="goods_id" resultType="java.lang.Integer" order="BEFORE">
		select -6
	</selectKey>
		insert into t_goods(title, sub_title, original_cost, current_price,discount, is_free_delivery, category_id)
		values(#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryID})
</insert>
<!--支持的情况-->
<insert id="insert" parameterType="com.zyf.mybatis.Entity.Goods" useGeneratedKeys="true" keyColumn="goods_id" keyProperty="goodsId">
	insert into t_goods(title, sub_title, original_cost, current_price,discount, is_free_delivery, category_id)
	values(#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryID})
</insert>
  1. 删除
<delete id="delete" parameterType="java.lang.Integer">
	delete from t_goods
	where goods_id = #{value}
</delete>
  1. 修改
<update id="update" parameterType="com.zyf.mybatis.Entity.Goods">
	update t_goods
	set title = #{title}, sub_title = #{subTitle}, original_cost = #{originalCost}, current_price = #{currentPrice},
	discount = #{discount}, is_free_delivery = #{isFreeDelivery}, category_id = #{categoryID}
	where goods_id = #{goodsId}
</update>
  1. 关联查询
    一个学生对应着唯一一个班级,一个班级对应着多个学生。这分别对应着多对一和一对多关系。可以使用关联查询来实现这个效果。
    一个GoodsGoodsDetail 对象对应着一个Goods对象
public class GoodsDetail {
    private Integer gdId;
    private Integer goodsId;
    private String gdPicUrl;
    private Integer gdOrder;
    private Goods goods;
}

多对一可以使用之前的resultMap实现,这里有更简单的方法(之前的复杂对象查询也可以使用这个方法)。
编写xml文件
新建goodsDetail.xml文件,并且设置其命名空间为goodsDetail。

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="goodsDetail">
    <select id="selectByGoodsId" parameterType="java.lang.Integer" resultType="com.zyf.mybatis.Entity.GoodsDetail">
        select * from t_goods_detail where goods_id = #{value}
    </select>
    <!-- 实现对象的映射 -->
    <resultMap id="rm" type="com.zyf.mybatis.Entity.GoodsDetail">
        <id property="gdId" column="gd_id" />
        <result property="goodsId" column="goods_id" />
        <!--将goods_id传入goods.selectById方法中并将结果包装成Goods对象然后赋值给goods属性-->
        <association property="goods" select="goods.selectById" column="goods_id" />
    </resultMap>
    <select id="manyToOne" resultMap="rm">
        select * from t_goods_detail limit 9,10
    </select>
</mapper>

一个Goods对应着多个GoodsDetail对象

public class Goods  implements Serializable {
    private Integer goodsId;
    private String title;
    private String subTitle;
    private Float originalCost;
    private Float currentPrice;
    private Float discount;
    private Integer isFreeDelivery;
    private Integer categoryID;
    private List<GoodsDetail> goodsDetails;
}

在goods.xml文件中添加

	<!--编写映射文件-->
   <resultMap id="rm" type="com.zyf.mybatis.Entity.Goods">
        <id property="goodsId" column="goods_id" />
        <!--将goods_id传入goodsDetail.selectByGoodsId方法中并将查询结果放入goodsDetails集合中-->
        <collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id" />
    </resultMap>
    <select id="oneToMany" resultMap="rm">
        select * from t_goods limit 1,10
    </select>

三、动态SQL

mybatis支持使用,,等标签动态生成SQL语句

    <select id="dynamicSelect" resultType="com.zyf.mybatis.Entity.Goods" parameterType="java.util.Map">
        select * from t_goods
        <!--生成的语句作为where的条件-->
        <where>
        	<!--满足条件则生成语句并拼接在where的条件中-->
            <if test="min != null">
                and current_price &gt; #{min}
            </if>
            <if test="max != null">
                and current_price &lt; #{max}
            </if>
        </where>
    </select>

四、mybatis中的二级缓存

执行查询语句时,mybatis需要对数据库中的数据进行读取,开销大,通过将之前的查询结果存放在内存中可以加速提高查询速度节省系统开销。mybatis支持二级缓存,默认情况下自动开启一级缓存,开启二级缓存时需要进行一定的配置。

  1. 一级缓存
    一级缓存的范围是一次session,在这个session中执行的查询语句的结果都会被放入当前session的一个缓冲区中,当再次查询之前的查询结果时会从缓冲区中读取这个对象。这个缓冲区别的session无法访问。
  2. 二级缓存
    开启二级缓存后,一个namespace中的查询语句产生的结果会被放在一个缓冲区中,当再次查询当前缓冲区中的对象时会直接去缓冲区中取。
    二级缓存需要手动开启,在标签下添加。其中eviction指定缓存的清除策略(相当于操作系统中进程的调度策略)。flushInterval指定缓存区的刷新时间,过了指定时间清除缓存。size设置缓存的大小(单位是对象,如果查询结果是一个集合则这个集合视为一个对象)。readOnly设置缓存区是否是只读,如果设置为true则获取是缓存中的对象,如果设置为false则返回的是缓存对象的拷贝。
  3. 缓冲的清空
    对于一级缓存,只要执行了commit就会清空缓存。对于二级缓存,执行了update,delete或者insert语句后再执行commit才会清空缓存。
    可以手动设置执行某条语句后清空缓存,在执行语句中添加flushCache=“true”,如:
    <select id="selectById" parameterType="Integer" resultType="com.zyf.mybatis.Entity.Goods" flushCache="true">
        select * from t_goods where goods_id = #{value}
    </select>
  1. 不放入缓冲区
    对于查询结果为集合的语句,这个集合被设为一个对象存放在缓冲区中,只有在查询结果与这个集合中的所有内容都相同时才会去缓冲区中取这个集合,命中率较低,因此可以设置某条查询语句的查询结果不放入缓冲区中,使用useCache=“false”,这个貌似不对一个session起作用。
    <select id="selectByPrice" parameterType="java.util.Map" resultType="com.zyf.mybatis.Entity.Goods" useCache="false">
        select * from t_goods where current_price between #{min} and #{max}
    </select>

五、pagehelper

pagehelper是国人开发的一款分页插件,网址https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md。使用前需要添加pagehelper和jsqlparser依赖,然后在mybatis的配置文件中设置使用pagehelper的拦截器,在标签内添加(需要注意添加的位置,放在标签之后,具体的顺序可以查看官网)。

    <plugins>
        <!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
            <property name="reasonable" value="true" />
            <property name="helperDialect" value="mysql" />
        </plugin>
    </plugins>

使用pagehelper

//设置业的大小为10并且查询第2页的数据
PageHelper.startPage(2, 10);
Page<Goods> page = (Page)session.selectList("goods.selectAll");
List<Goods> list = page.getResult();

六、使用C3P0连接池

首先添加c3p0的依赖(groupId为com.mchange),编写类

package com.zyf.mybatis.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;

public class C3PODataSourceFactory extends UnpooledDataSourceFactory {
    public C3PODataSourceFactory(){
    	//使用C3P0的数据源
        this.dataSource = new ComboPooledDataSource();
    }
}

修改配置文件

<?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>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <environments default="proud">
    	//之前使用的配置,注意这里的dataSource 类型为POOLED,这也是为什么前面编写的类继承UnpooledDataSourceFactory
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
      	//使用C3P0数据源时的配置
        <environment id="proud">
            <transactionManager type="JDBC"/>
            <dataSource type="com.zyf.mybatis.datasource.C3PODataSourceFactory">
                <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
                <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/babytun?useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="user" value="root"/>
                <property name="password" value="root"/>
                <property name="maxPoolSize" value="10" />
                <property name="minPoolSize" value="10" />
                <property name="initialPoolSize" value="10" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/goods.xml" />
        <mapper resource="mappers/goodsDetail.xml" />
    </mappers>
</configuration>

七、批处理

JDBC支持批处理,在mybatis可以动态生成SQL语句来执行批量删除、插入。
批量删除:

<!--传入List集合-->
<delete id="batchDelete" parameterType="java.util.List">
	delete from t_goods
	where goods_id in
	<!--循环生成SQL语句, collection指定传入的集合对象,item当前循环的元素,index当前是第几次循环,separator指定每次产生语句后面的分割符(会自动删除末尾分割符),open指定在循环产生的语句最前面添加(, close指定在循环产生的语句最后面添加)-->
	<foreach collection="list" item="item" index="index" separator="," open="(" close = ")">
		#{item}
	</foreach>
</delete>

批量更新

<insert id="batchInsert" parameterType="java.util.List">
	insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
	values
	<foreach collection="list" item="item" index="index" separator=",">
		(#{item.title}, #{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #					{item.categoryID})
	</foreach>
</insert>

八、基于注解编写SQL语言

基于注解编写SQL语言需要先建立一个借口,然后在接口的方法上添加注解。在执行查询时(要在mybatis的配置文件中开启对这个接口的扫描。用啦扫描某个mapper文件,用来扫描接口,<package name=" />用来扫描整个包),使用session.getMapper(接口名.class)来获取接口实例化后的对象(这里mybatis使用了类似动态代理的机制),执行实例化后的对象的方法。
编写GoodDAO

public interface GoodsDAO {
	//查询语句,这里@Param("min")Integer min指定#{min}取值于min
    @Select("select * from t_goods where current_price between #{min} and #{max}")
    List<Goods> selectByPrice(@Param("min")Integer min, @Param("max")Integer max);
	//插入语句
    @Insert("insert into t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)\n" +
            "values(#{title}, #{subTitle}, #{originalCost}, #{currentPrice}, #{discount}, #{isFreeDelivery}, #{categoryID})")
    @SelectKey(statement = "select last_insert_id()", before = false, keyProperty = "goodsId", resultType = Integer.class)
    void insert(Goods goods);
}

使用查询语句:

@Test
public void testSelectByPrice(){
	SqlSession session = null;
	try {
		session = MyBatisUtil.openSession();
		GoodsDAO goodsDAO = session.getMapper(GoodsDAO.class);
		List<Goods> list = goodsDAO.selectByPrice(500, 600);
		for(Goods g : list)
			System.out.println(g);
		}catch (Exception e){
			e.printStackTrace();
		}finally {
			if(session != null)
			session.close();
	}
}

使用插入语句

@Test
public void testInsert(){
	SqlSession session = null;
	try {
		session = MyBatisUtil.openSession();
		Goods goods = new Goods("三鹿奶粉", "国产", (float) 699, (float) 699, (float) 1, 1, 21);
		GoodsDAO dao = session.getMapper(GoodsDAO.class);
		dao.insert(goods);
		System.out.println(goods);
	}catch (Exception e){
		e.printStackTrace();
	}finally {
	if(session != null)
		session.close();
	}
}

对复杂查询结果进行映射需要使用@Results注解

@Select("select g.*, c.* from t_goods g, t_category c where g.category_id = c.category_id")
@Results({
	@Result(column = "goods_id", property = "goods.goodsId", id = true),
	@Result(property="goods.title", column="title"),
	@Result(property="goods.subTitle", column="sub_title" ),
	@Result(property="goods.originalCost", column="original_cost"),
	@Result(property="goods.currentPrice", column="current_price" ),
	@Result(property="goods.discount", column="discount"),
	@Result(property="goods.isFreeDelivery", column="is_free_delivery" ),
	@Result(property="goods.categoryID", column="category_id")
})
List<GoodsDTO> selectGoodsDTO();

九、手动实现Mybatis

  • Mapper接口类
public interface UserMapper {
    @Select("select * from user where name = #{name}")
    public List<User> getUser(@Param("name") String name);
    @Select("select * from user where id = #{id}")
    public User getUserById(@Param("id") Integer id);
}
  • 获取UserMapper的实现类
public class Test {
    public static void main(String[] args) {
    	//通过MapperProxyFactory获取UserMapper的实现类
        UserMapper mapper = MapperProxyFactory.getMapper(UserMapper.class);
        //调用mapper类的实现类的方法获取sql的执行结果
        List<User> zhouyu = mapper.getUser("zhouyu");
        System.out.println(zhouyu);
        User user = mapper.getUserById(1);
        System.out.println(user);
    }
}
  • MapperProxyFactory的实现。MapperProxyFactory.getMapper(UserMapper.class)会生成UserMapper的一个实现对象,这个对象的方法中通过JDBC实现了对数据库的操作。
public class MapperProxyFactory {
    private static Map<Class, TypeHandler> typeHandlerMap = new HashMap<>();
    static {
        typeHandlerMap.put(String.class, new StringTypeHandler());
        typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    //getMapper方法会创建一个Mapper接口的实现类
    @SuppressWarnings("unchecked")
    public static <T> T getMapper(Class<T> mapper) {
    	//调用Proxy的newProxyInstance()方法创建一个实现类
        return (T) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
        	//invoke方法包含了调用JDBC执行SQL语句
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 结果支持List或单个User对象
                Object result = null;
                // 参数名:参数值
                // 相当于 变量名:变量值,替换到sql中
                Map<String, Object> paramValueMapping = new HashMap<>();
                //获得方法得参数
                Parameter[] parameters = method.getParameters();
                //JDK1.8之前无法获取参数得真正名字,会得到类似arg1,arg2这样得形式,这就需要在方法得参数前添加注解@Param指定参数名
                for (int i = 0; i < parameters.length; i++) {
                    Parameter parameter = parameters[i];
                    //将参数得值以arg1:参数值得形式存放在map中
                    paramValueMapping.put(parameter.getName(), args[i]);
                    //将参数得值以变量名:参数值得形式存放在map中
                    paramValueMapping.put(parameter.getAnnotation(Param.class).value(), args[i]);
                }
                //获取注解上得SQL语句
                String sql = method.getAnnotation(Select.class).value();
                // 由于SQL语句上使用了#{},需要将#{}替换为?,此外需要记录?对应得#{}中得变量名
                ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
                GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
                //将将#{}替换为?
                String parseSql = parser.parse(sql);
                // 创建数据库连接
                Connection connection = getConnection();
                PreparedStatement statement = connection.prepareStatement(parseSql);
                //对一个一个问好赋值,getParameterMappings()会返回一个List,List中第i个元素对应则第i个#{}中得变量名
                for (int i = 0; i < tokenHandler.getParameterMappings().size(); i++) {
                    // sql中的#{}变量名
                    String property = tokenHandler.getParameterMappings().get(i).getProperty();
                    Object value = paramValueMapping.get(property); // 变量值
                    //根据变量类型查找typeHandler,用来为statement设置值
                    TypeHandler typeHandler = typeHandlerMap.get(value.getClass()); // 根据值类型找TypeHandler
                    typeHandler.setParameter(statement, i + 1, value);
                }
                // 3、执行PreparedStatement
                statement.execute();
                // 4、封装结果
                List<Object> list = new ArrayList<>();
                ResultSet resultSet = statement.getResultSet();
                Class resultType = null;
                Type genericReturnType = method.getGenericReturnType();
                if (genericReturnType instanceof Class) {
                    // 不是泛型
                    resultType = (Class) genericReturnType;
                } else if (genericReturnType instanceof ParameterizedType) {
                    // 是泛型
                    Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                    resultType = (Class) actualTypeArguments[0];
                }
                // 根据setter方法记录 属性名:Method对象
                Map<String, Method> setterMethodMapping = new HashMap<>();
                for (Method declaredMethod : resultType.getDeclaredMethods()) {
                    if (declaredMethod.getName().startsWith("set")) {
                        String propertyName = declaredMethod.getName().substring(3);
                        propertyName = propertyName.substring(0, 1).toLowerCase(Locale.ROOT) + propertyName.substring(1);
                        setterMethodMapping.put(propertyName, declaredMethod);
                    }
                }
                // 记录sql返回的所有字段名
                ResultSetMetaData metaData = resultSet.getMetaData();
                List<String> columnList = new ArrayList<>();
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    columnList.add(metaData.getColumnName(i + 1));
                }
                while (resultSet.next()) {
                    Object instance = resultType.newInstance();
                    for (String column : columnList) {
                        Method setterMethod = setterMethodMapping.get(column);
                        // 根据setter方法参数类型找到TypeHandler
                        TypeHandler typeHandler = typeHandlerMap.get(setterMethod.getParameterTypes()[0]);
                        setterMethod.invoke(instance, typeHandler.getResult(resultSet, column));
                    }
                    list.add(instance);
                }
                // 5、关闭数据库连接
                connection.close();
                if (method.getReturnType().equals(List.class)) {
                    result = list;
                } else {
                    result = list.get(0);
                }
                return result;
            }
        });
    }
    private static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/tuling?characterEncoding=utf-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai",
                "root", "Zhouyu123456***");
        return connection;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值