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"?>
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="jdbc.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="${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"?>
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="model.User">
<insert id="add" parameterType="model.User">
insert into t_user (username,password,nickname) value (#{username},#{password},#{nickname})
insert> mapper> |
对于上面的insert标签中,只要我们传入的参数是model包中的User类,就必须让parameterType属性的值为model.User,很麻烦。其实我们可以将下面的话添加到mybatis-config.xml文件中,注意,必须添加到properties标签后面,这些标签是有顺序的: <typeAliases> <typeAlias type="model.User" alias="User"/> typeAliases> 此时我们就能将parameterType的值写成User。 可是我的model包中有很多类,如果懒得一条一条添加映射,可以这么写: <typeAliases> <package name="model"/> typeAliases> 这样程序就会把model包中的所有类遍历一遍,只要在这个包中的所有类作为传入参数时就都不用写包名。 |
第四步:首先我们先把mapper文件加入到配置文件中,于是原来的mybatis-config文件就变成了如下;
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="jdbc.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="${user}"/> <property name="password" value="${password}"/> dataSource> environment> environments>
<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 } |
然后修改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> <if test="name!=null">username like #{name}if> <if test="type!=null">and type=#{type} if> where>
<if test="sort!=null"> order by ${sort} <choose> <when test="order!=null">${order}when> <otherwise>ascotherwise> 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">
<result column="post_code" property="postcode"/> resultMap> <select id="loadById" parameterType="int" resultMap="addressResultMap"> 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">
<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">
<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"> 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 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赋值。