mybatis的熟练运用以及反射知识讲解

JSP常用设计模式MVC模式——Mybatis

mybatis的使用

我们在写项目的时候必定要写DAO,写DAO的时候不难发现对每张表的DAO都差不多,只是sql语句不同,DAO中的每个方法其实也差不多,所以直接用JDBC写DAO是在太麻烦,今天我们就介绍一种很实用的框架——mybatis,这个框架能让我们更愉快地写DAO。

3集 :mybatis使用步骤

第一步:先创建一个xml的配置文件来说明和数据库的连接,一般在项目的src下创建。下面的代码是基本的mybatis语句,懒得每个标签写注释了,自己去查帮助文档吧,都有;

<?xml version="1.0" encoding="UTF-8"?>

<!--下面这句话作用就是限制xml文件按照某种语法来进行编辑,有了这句话,我们在联网的状态下编辑这个xml文件时就会有代码提示  -->

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<properties resource="jdbc.properties"/><!-- 导入配置文件jdbc.properties 中的内容-->

<environments default="development"><!--此时,默认使用的环境是名为development的环境-->

<environment id="development">

<transactionManager type="JDBC"/><!--设置事务管理模式为JDBC-->

<dataSource type="POOLED">

<property name="driver" value="${driver}"/>

        <property name="url" value="${url}"/>

        <property name="username" value="${user}"/>

        <property name="password" value="${password}"/>

</dataSource>

</environment>

</environments>

</configuration>

jdbc.properties中的内容

user=phoenix

password=123

url=jdbc:mysql://localhost:3306/myshop

driver=com.mysql.jdbc.Driver

第二步:连接完成了,接下来我们就要根据我们建好的数据库的表创建实体类,比如说我创建了一个用户User的实体类,为了省空间咱就把GET和SET方法去了;

public class User {

private int id;

private String username;

private String password;

private String nickname;

private boolean type;

}

第三步:创建mapper文件完成对实体类的映射,该文件同样是xml文件,比如说我们创建一个User的mapper文件User.xml;

<?xml version="1.0" encoding="UTF-8"?>

<!--下面这句话是加入mapperdtd文件,和之前的连接配置文件已经不同了  -->

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="model.User"><!--这个namespace我们可以自定义一个名称-->

<!--下面的程序就是往数据库添加User记录的语句-->

<insert id="add" parameterType="model.User"><!--id是名称的意思,parameterType意思是传入参数的类型,我们这里插入的是User类型的记录,所以parameterType的值是model.User  -->

     <!--下面的语句中#{}中的内容就是model包中User类的属性名,mybatis会调用花括号中的属性名的getter方法来获取传入的User类型的参数的对应属性值,从而添加到数据库中-->

insert into t_user (username,password,nickname) value

                          (#{username},#{password},#{nickname})

 <!--mybatis#{}的取值方式和PreparedStatement中的?是一样的,如果传入的是字符串,会自动带上单引号加到SQL语句中;mybatis还有一种取值方式就是${},这种就是原样导入,不加任何修饰,当我们要对提取结果按某个字段排序时就要用到order by后面跟字段名,此时就需要这种原样导入方式来导入字段名-->

 <!--每个sql标签中我们只能有一个传入参数,当传入参数是基本数据类型或String时,此时标签中的sql语句只可能有一个待传参数,所以此时#{}中的内容可以任意设置,反正只有一个传入参数和一个待传参数,跑不了;那我们想传入多个基本参数怎么办呢,那就传入一个Map类型的参数,将要传入的基本类型参数塞进Map中,#{}中的内容必须和Map中的key一致;当传入参数是一个对象时比如User,此时#{}中的内容就必须是User的属性名。-->

</insert>

</mapper>

对于上面的insert标签中,只要我们传入的参数是model包中的User类,就必须让parameterType属性的值为model.User,很麻烦。其实我们可以将下面的话添加到mybatis-config.xml文件中,注意,必须添加到properties标签后面,这些标签是有顺序的

<typeAliases>

<typeAlias type="model.User" alias="User"/><!--为类型model.User定义一个别名为User,这里这么做是为了在mapper文件中写DAO时某传入参数如果是model.User就可以简写成User-->

</typeAliases>

此时我们就能将parameterType的值写成User。

可是我的model包中有很多类,如果懒得一条一条添加映射,可以这么写:

<typeAliases>

<package name="model"/>

</typeAliases>

这样程序就会把model包中的所有类遍历一遍,只要在这个包中的所有类作为传入参数时就都不用写包名。

第四步:首先我们先把mapper文件加入到配置文件中,于是原来的mybatis-config文件就变成了如下;

<?xml version="1.0" encoding="UTF-8"?>

<!--下面这句话作用就是限制xml文件按照某种语法来进行编辑,有了这句话,我们在联网的状态下编辑这个xml文件时就会有代码提示  -->

<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

<properties resource="jdbc.properties"/><!-- 导入配置文件jdbc.properties 中的内容-->

<environments default="development">

<environment id="development">

<transactionManager type="JDBC"/><!--设置事务管理为JDBC-->

<dataSource type="POOLED">

<property name="driver" value="${driver}"/>

        <property name="url" value="${url}"/>

        <property name="username" value="${user}"/>

        <property name="password" value="${password}"/>

</dataSource>

</environment>

</environments>

<!--将mapper文件加入到配置文件中  -->

<mappers>

<mapper resource="model/User.xml"/>

</mappers>

</configuration>

然后我们创建SQLSession,并且通过SQLSession完成对数据库的操作;

import java.io.IOException;

import java.io.InputStream;

import org.apache.ibatis.io.Resources;

import org.apache.ibatis.session.SqlSession;

import org.apache.ibatis.session.SqlSessionFactory;

import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class Test {

public static void main(String[] args) {

try {

//1、创建配置文件的输入流

InputStream is=Resources.getResourceAsStream("mybatis-config.xml");

//2、创建SQLSessionFactory

SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(is);

//3、创建SQLSession

SqlSession session=factory.openSession();

//4、调用mapper文件插入数据(调用之前要将mapper文件加入到mybatis-config.xml配置文件之中)

User u=new User();

u.setUsername("李白");u.setPassword("123");u.setNickname("诗仙");

session.insert("model.User.add", u);//第一个参数是User的mapper文件中的namespace的值和insert的Id值为了使不把程序写死,第一个参数也可以写成User.class.getName()+.add

session.commit();//提交事务

session.close();//提交完关闭事物

} catch (IOException e) {

e.printStackTrace();

}

}

}

 

还有一种方法可以完成对数据库的操作——基于mapper的访问方式:

首先我们创建一个名为UserMapper的Interface,Interface中的方法名必须和User.xml中mapper中的方法标签中的id属性完全一样

public interface UserMapper {

public void add(User u);

public void delete(int id);

public void update(User u);

public User load(int id);

}

然后修改User的mapper文件User.xml,mapper标签的namespace属性必须修改成上面接口的路径,如果上面的UserMapper在Interface包中,那么我们就修改成:

<mapper namespace="Interface.UserMapper">

接着在用SqlSession调用添加方法的时候就可以这么调用

session.getMapper(UserMapper.class).add(u);

 

在这种调用方式下,我们介绍一种超级牛逼的操作——基于Annotation的数据库操作,我们可以直接对UserMapper接口进行如下改写:

public interface UserMapper {

@Insert("insert into t_user (username,password,nickname) value (#{usename},#{password},#{nickname})")

public void add(User u);

 

@Delete("delete from t_user where id=#{id}")

public void delete(int id);

 

@Update("update t_user set username=#{username},password=#{password},nickname=#{nickname} where id=#{id}")

public void update(User u);

 

@Select("select * from t_user where id=#{id}")

public User load(int id);

 

@Select("select * from t_user")

public List<User> list();

}

然后修改mybatis-config.xml文件,将原来导入配置文件的mapper全部删掉,然后将mappers标签改成如下:

<mappers>

<mapper class="Interface.UserMapper"/>

</mappers>

调用方法的时候就用上面介绍的SqlSession调用增删改查方法。这种方式直接省去了为每一个类创建mapper文件的麻烦,直接创建接口,在接口上添加sql语句就行。

 

动态SQL语句:

直接上例子,这里是常用的,其它只是直接找帮助文档

<select id="find" parameterType="map" resultType="User">

select * from t_user

<where><!--where语句中的内容-->

<if test="name!=null">username like #{name}</if>

<if test="type!=null">and type=#{type} </if><!--如果前面的name为空,而这里的type不为空,这里的sql语句会自动去掉and -->

</where>

        <!-- 上面这一段还可以这样写,这种写法很聪明,要记住

         select * from t_user where 1=1

<if test="type!=null">and type=#{type}</if>

<if test="name!=null">and name like #{name}</if> -->

<if test="sort!=null">

 order by ${sort}

 <choose>

  <when test="order!=null">${order}</when>

  <otherwise>asc</otherwise><!--这个就相当于if else-->

 </choose>

 </if>

limit #{pagerOffset},#{dataCount}

</select>

 

resultMap的讲解:

在我们的shop01项目中,我们在数据库中创建了一个表t_Address,表的创建信息如下:

create table t_address(

id int(11) primary key auto_increment,

name varchar(255),

phone varchar(100),

postcode varchar(100),

user_id int(11),

constraint foreign key (user_id) references t_user(id)

);

相对应的,我们也创建了一个Address的类,类信息如下(为了方便咱就把getset方法省略了):

public class Address {

private int id;

private String name;

private String phone;

private String postcode;

private User user;

}

这个时候,我们在Address的Mapper文件中写了一个loadById的方法:

<select id="loadById" parameterType="int" resultType="Address">

select * from t_address where id=#{id}

</select>

语句很简单,就是传入参数是int类型,然后返回值是Address。那么,mybatis是怎么把sql语句搜索到的结果返回成一个Address对象呢?

其实mybatis背地里干了这么一件事:它看到搜索结果中有id字段,那就去Address中去找有没有setId方法,有就把id字段的值通过setId方法赋给Address的id属性,接着看到有name字段,就去找有没有setName方法,这样以此类推。这是一种依赖注入的赋值方式,其实id字段的值能不能赋出去,关键看有没有setId方法,而无关乎Address是否有个名为id的属性,但我们一般情况下还是不要调皮,好好设置属性,使用编辑器的GetSet功能就行,一定要让属性名和GetSet方法的名称一一对应。

那此时我将数据表中的postcode字段改成post_code后再来运行loadById,mybatis就会去找有没有setPost_code方法,而此时Address中根本没有setPost_code方法,于是post_code的值就给不了。

这只是我们自己编编的小项目,到大项目的时候数据表的字段名几乎不可能和类名中的属性名一致,那怎么办呢?解决办法有两种:

第一种就是直接对搜索结果的字段名重命名

<select id="loadById" parameterType="int" resultType="Address">

select *,post_code as ‘postcode’ from t_address where id=#{id}

</select>

还有一种就是我们要讲的resultMap

<resultMap id="addressResultMap" type="Address"><!--type是字段名要映射到的类的类名 -->

<!--这里的result标签是为普通字段设置映射用的,column是字段名,property是要映射的类中的属性名 。还有一种专为主键设置映射用的标签,就是id标签,设置主键的映射使用这个标签会提高整体效能-->

<result column="post_code" property="postcode"/>

</resultMap>

<select id="loadById" parameterType="int" resultMap="addressResultMap"><!--这里的resultMap的值就是上面resultMap的id值  -->

select * from t_address where id=#{id}

</select>

我们知道,上面的Address类中是没有user_id属性的,只有User类型的user属性,而搜索结果中只有user_id字段,那么用resultMap的association标签就能帮我们通过user_id字段的值来获得整个User

<resultMap id="addressResultMap" type="Address"><!--type是字段名要映射到的类的类名 -->

<!--property是Address类的某个要关联的属性名,column是数据表中的字段名,javaType是设置将选择结果封装成什么类型,select是选用哪个mapper中的哪个方法 -->

<association property="user" column="user_id" javaType="User" select="model.User.loadById"></association>

</resultMap>

使用这个方法mybatis实际上是发出了两条sql语句,一条搜索t_address,一条搜索t_user。所以使用这种方法不好的地方就是效率不高,所以我们一般使用下面的方法得到User对象:

<resultMap id="addressResultMap" type="Address" autoMapping="true">

<!--resultMap中如果有association标签的话,resultMap中未声明映射的字段就不会自动匹配类中的属性 ,所以这里就必须设置autoMapping为true。然而association标签中为声明映射的字段依然不会自动匹配,所以我们不得不手动设置映射-->

<id column="a_id" property="id"/>

<association property="user" javaType="User">

<id column="id" property="id"/>

<result column="username" property="username"/>

<result column="password" property="password"/>

<result column="nickname" property="nickname"/>

<result column="type" property="type"/>

</association>

</resultMap>

<select id="loadById" parameterType="int" resultMap="addressResultMap"><!--这里的resultMap的值就是上面resultMap的id值  -->

select *,t_address.id as 'a_id'

from t_address left join t_user on (t_address.id=t_user.id)

where t_address.id=#{id}

</select>

当然也可以这么干,这样就有了一个公用的User的resultMap

<resultMap id="addressResultMap" type="Address" autoMapping="true">

<id column="a_id" property="id"/>

<association property="user" javaType="User" resultMap="UserMap">

</association>

</resultMap>

<resultMap id="UserMap" type="User">

<id column="id" property="id"/>

<result column="username" property="username"/>

<result column="password" property="password"/>

<result column="nickname" property="nickname"/>

<result column="type" property="type"/>

</resultMap>

<select id="loadById" parameterType="int" resultMap="addressResultMap">

select *,t_address.id as 'a_id'

from t_address left join t_user on (t_address.id=t_user.id)

where t_address.id=#{id}

</select>

 

工厂模式——反射知识

以前我们创建一个实例都是很简单地new就行了,今天我们讲一个很牛逼的新的实例创建模式。首先我们创建一个User类,getset方法就省略了:

public class User {

private int id;

private String name;

 public void show(String str){

 System.out.println(str+","+this.id+", "+this.name);

 }

 

 public static void say(String str, int num){

 System.out.println("天上的"+str+"有"+num+"颗");

 }

}

接着我们就直接Test(catch实在太多了,我们也省了):

public class Test {

public static void main(String[] args) {

String str="itta.zttc.model.User";

try {

Class cla=Class.forName(str);

User u=(User)cla.newInstance();

u.setId(1);u.setName("新的创建对象方式");

u.show("hello");//正常输出

//新的函数调用方式

Method method=cla.getMethod("show", String.class);//第一个参数是函数名称的字符串,第二个参数是函数的参数,个数可以是1个或无限多个

//第一个参数是调用这个函数的对象,第二个参数是传入参数,就相当于u.show("hello2");

method.invoke(u, "hello2");//正常输出

//还可以调用静态函数,调用的时候invoke方法的第一个参数必须写类的Class形式

method=cla.getMethod("say",String.class,int.class);

method.invoke(cla, "星星",5);//正常输出

}

}

 

工厂1:

架构图

 

DAOFactory程序:

public class DAOFactory {

public static UserDAOInterface getUserDAO(){

return UserDAO.getUserDAO();

}

public static AddressDAOInterface getAddressDAO(){

return AddressDAO.getAddressDAO();

}

}

在程序中要用到DAO时,就通过DAOFactory来获得,要换针对不同数据库的DAO就直接在DAOFactory中改就行了。

工厂2:

架构图:

 

DAOFactoryUtil的getFactory程序:

public static IDAOFactory getFactory(){

IDAOFactory factory=null;

try {

Class cla=Class.forName(p.getProperty("factory"));

Method method=cla.getMethod("getDAOFactory");//这里提取的方法是目标DAOFactory的获取单例的方法

factory=(IDAOFactory)method.invoke(cla);

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (NoSuchMethodException e) {

e.printStackTrace();

} catch (SecurityException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

return factory;

}

在程序中要用到DAO 时,我们需要通过DAOFactoryUtil的getFactory方法来获得我们想要的DAOFactory,然后通过得到的DAOFactory得到相应的DAO,然后运用DAO的某个方法。举个例子:DAOFactoryUtil.getFactory().getUserDAO().add(user);

工厂3:

架构图:

 

DAOFactory中的getDAO方法程序:

private static Map<String,Object> params=new HashMap<String,Object>();

public static Object getDAO(String name){

Properties p=PropertiesUtil.getProperties();

Object obj=null;

try {

String str=p.getProperty(name);

if(params.containsKey(str)){

return params.get(str);

}

Class cla=Class.forName(str);

obj=cla.newInstance();

params.put(str,obj);

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

return obj;

}

程序中的params是为了实现单例,所以其实架构图中的UserDAO和AddressDAO可以不用单例。如果UserDAO和AddressDAO中用了单例,那就必须在这里获取到他们的单例获取方法才行。

在程序中要用到某个DAO时,我们就可以这么用:

IUserDAO uDAO=(IUserDAO)DAOFactoryUtil.getFactory().getDAO(“UserDAO”);

依赖注入:

当一个项目有很多数据表时,我们就必须建立很多对应的DAO,这些DAO都继承于BaseDAO。当我们对某个数据表执行某个操作时,可能必须先对另一个数据表执行操作,例如我们在留言项目中遇到过删除留言之前必须先删除留言的评论才行,这种情况下就必须在留言DAO中创建一个评论的DAO。

我们一般的创建方法就是在留言DAO中创建一个ICommentDAO,然后用上面的方法得到相应的ICommentDAO,然后在留言DAO的增删改查方法中用到评论DAO时就用这个ICommentDAO就行了。

但是我们要介绍一种新方式去给ICommentDAO赋值,就是在留言DAO中先private ICommentDAO commentDAO;,然后创建这个属性的set方法。

接着我们创建一个Annotation(下面的UserDAO就看成CommentDAO就行,懒得改了):

/*

 * 我们使用这个Annotation来标注需要进行依赖注入的方法

 * 如果方法上面是@shopAnnotation("userDAO")就说明应该注入UserDAO对象

 * 如果方法上面是@shopAnnotation就说明该方法使用setXXX来注入,如果方法是setUserDAO表示注入UserDAO对象

 * */

@Retention(RetentionPolicy.RUNTIME)//这句话表示在运行时刻执行Annotation,没有这句话的话程序只会在编译的时候检测这个Annotation,运行时刻是不会检测的

public @interface shopAnnotation {

/*

 * 表示为这个Annotation加上了一个名为str的属性,default ""表示这个属性默认值为"",如果没有default,那必须在使用这个Annotation的时候这样定义

 * @shopAnnotation(str="hello")

 * */

String str() default "";

/*

 * value是Annotation唯一的默认属性,在定义value的值的时候可以直接这样定义@shopAnnotation("hello")

 * 特别注意:当要为两个以上的属性赋值的时候,默认属性的直接定义特权就不起作用了,也就是说@shopAnnotation("hello",str="world")是会报错的

 * */

String value() default "";

}

 

接着我们在BaseDAO中创建一个构造方法:

public BaseDAO(){

Method[] methods=this.getClass().getDeclaredMethods();

for(Method method:methods){

if(method.isAnnotationPresent(shopAnnotation.class)){//判断方法头上是否有某个Annotation的约束,没有我们预设的Annotation那就不用理这个方法

shopAnnotation sA=method.getAnnotation(shopAnnotation.class);

String value=sA.value();

if(value==null||"".equals(value.trim())){//判断方法上的Annotation是不是有value值

if(method.getName().startsWith("set")){

value=method.getName().substring(3);

}

}

if(value!=null){

Object obj=DAOFactory.getDAO(value);

try {

method.invoke(this, obj);//这里的传入参数我有点问题

} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {

e.printStackTrace();

}

}

}

}

}

然后我们在留言DAO中的setCommentDAO()方法上面加上我们的Annotation,可以为value赋值为CommentDAO,也可以不赋值。这样当留言DAO的单例初始化的时候,就会调用setCommentDAO方法为ICommentDAO赋值。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值