内容简介:本文旨在用最通俗的语言讲述最枯燥的基本知识当项目框架SSH(spring Struts hibernate)日落西山时,SSM(spring SpringMVC、MyBatis)就大行其道,大部分项目都渐渐转至SSM,因此mybatis也成了Java程序员的必学之术,本文就mybatis的语法做一次小小的总结,旨在让读者用最少的时间学会使用MyBatis。MyBatis的前身是Apache的一个开源项目ibatis,后来迁移到Google code就改名为MyBatis。
本文旨在用最通俗的语言讲述最枯燥的基本知识
当项目框架SSH(spring Struts hibernate)日落西山时,SSM(spring SpringMVC、MyBatis)就大行其道,大部分项目都渐渐转至SSM,因此mybatis也成了 Java 程序员的必学之术,本文就mybatis的语法做一次小小的总结,旨在让读者用最少的时间学会使用MyBatis。
文章提纲:
- 什么是MyBatis
- MyBatis的引入
- MyBatis的初始化配置
- MyBatis的 SQL 语法
- 运行原理和实操一波
1. 什么是MyBatis
MyBatis的前身是Apache的一个开源项目ibatis,后来迁移到Google code就改名为MyBatis。
用网上已经说烂了的话来说就是:
MyBatis是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。
2. MyBatis的引入
- 如果是传统的的项目,则直接下载相应jar包引入到项目中即可,下载地址为:
1http://central.maven.org/maven2/org/mybatis/mybatis/3.4.6/mybatis-3.4.6.jar 复制代码
- 如果为maven构建的项目,则只需要在pom.xml中加入以下依赖然后reimport一下即可:
1<dependency> 2 <groupId>org.mybatis</groupId> 3 <artifactId>mybatis</artifactId> 4 <version>x.x.x</version> 5</dependency> 复制代码
- 如果是gradle构建的项目,则只需要在配置中添加以下代码:
1// https://mvnrepository.com/artifact/org.mybatis/mybatis 2compile group: 'org.mybatis', name: 'mybatis', version: '3.4.6' 复制代码
3. MyBatis的配置和初始化
在引入mybatis之后,接下来需要学习的mybatis的配置,虽然现在流行的框架像springboot等已经不需要用XML方式进行配置,但作为一名新手,我们还是需要学习一些关于mybatis的配置的解释,这样有助于我们理解mybatis的原理。
mybatis的基本配置:
1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE configuration
3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
4<configuration>
5 <!--properties用于定义一些属性变量,以便在配置文件中调用-->
6 <properties>
7 <!--定义一个变量为driver的属性,在下面就可以用${driver}来获得其属性值-->
8 <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
9 <property name="url" value="jdbc:mysql://10.0.0.11/test"></property>
10 </properties>
11 <!--定义不同环境下的配置,便于区分生产、测试等环节的配置-->
12 <environments default="development">
13 <!--定义一个环境下的配置-->
14 <environment id="development">
15 <transactionManager type="JDBC"/>
16 <dataSource type="POOLED">
17 <property name="driver" value="${driver}"/>
18 <property name="url" value="${url}"/>
19 <property name="username" value="root"/>
20 <property name="password" value="1111"/>
21 </dataSource>
22 </environment>
23 </environments>
24 <!--用于设置mapper文件的引入-->
25 <mappers>
26 <!--resource方式引入mapper文件-->
27 <mapper resource="mapper/UserMapper.xml"/>
28 </mappers>
29</configuration>
复制代码
这是一个标准的mybatis的配置文件,很多情况下,这个配置已经足够,但是为了在以后的使用有更好的认识,下面讲解配置文件中configuration标签下的常用子标签:
- properties标签:用于定义一些通用属性,便于配置文件中使用
- settings标签:用于设置一些改变MyBatis运行时行为的配置
- environments标签:用于配置成适应多种环境
- mappers标签:用于mapper映射器的设置
下面分别对每个标签做简单讲解:
1.properties标签
当我们需要把一些值作为一个变量被配置中使用时,就可以在properties标签下增加一个property标签,其中属性name是指变量名称,属性value是值,如:
1 <properties> 2 <property name="driver" value="com.mysql.cj.jdbc.Driver"></property> 3 </property> 复制代码
定义好之后,就可以在配置文件中使用了,如:
1<dataSource type="POOLED">
2 <property name="driver" value="${driver}"/>
3</dataSource>
复制代码
2.settings标签
settings标签中的每一个setting都是用于调整mybatis的运行行为,我们在需要使用其中某些setting时加入即可,其常用的配置以及各个setting的解释如下:
1<settings> 2 #设置配置文件中的所有映射器已经配置的任何缓存,默认false。 3 <setting name="cacheEnabled" value="true"/> 4 #延迟加载的全局开关。当开启时,所有关联对象都会延迟加载,默认为false 5 <setting name="lazyLoadingEnabled" value="true"/> 6 #是否允许单一语句返回多结果集,默认为true 7 <setting name="multipleResultSetsEnabled" value="true"/> 8 #是否使用列标签代替列名,默认为true 9 <setting name="useColumnLabel" value="true"/> 10 #是否允许JDBC支持自动生成主键,默认为false 11 <setting name="useGeneratedKeys" value="false"/> 12 #指定 MyBatis 应如何自动映射列到字段或属性 13 <setting name="autoMappingBehavior" value="PARTIAL"/> 14 #指定发现自动映射目标未知列(或者未知属性类型)的行为,默认NONE 15 #NONE: 不做任何反应 16 #WARNING: 输出提醒日志 17 #FAILING: 映射失败 (抛出 SqlSessionException) 18 <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> 19 #配置默认的执行器。默认为SIMPLE 20 #SIMPLE 就是普通的执行器; 21 #REUSE 执行器会重用预处理语句; 22 #BATCH 执行器将重用语句并执行批量更新 23 <setting name="defaultExecutorType" value="SIMPLE"/> 24 #设置超时时间,它决定驱动等待数据库响应的秒数。 25 <setting name="defaultStatementTimeout" value="25"/> 26 #为驱动的结果集获取数量(fetchSize)设置一个提示值 27 <setting name="defaultFetchSize" value="100"/> 28 #是否允许在嵌套语句中使用分页。如果允许使用则设置为false。 29 <setting name="safeRowBoundsEnabled" value="false"/> 30 #是否开启自动驼峰命名规则(camel case)映射,默认为false 31 <setting name="mapUnderscoreToCamelCase" value="false"/> 32</settings> 复制代码
3. environments
environments是为了配置多环境数据源而生,在我们定义好了各种环境之后,只需要在代码中设置从哪个环境中加载数据源即可,或者修改environments标签中的default也可以达到切换环境的效果。
environments的基本配置如下:
1<environments default="development">
2 #定义一个名称为development的环境配置
3 <environment id="development">
4 #设置事务管理器的类型,有JDBC和MANAGED梁总
5 <transactionManager type="JDBC">
6 <property name="..." value="..."/>
7 </transactionManager>
8 #数据源设置
9 <dataSource type="POOLED">
10 <property name="driver" value="${driver}"/>
11 <property name="url" value="${url}"/>
12 <property name="username" value="${username}"/>
13 <property name="password" value="${password}"/>
14 </dataSource>
15 </environment>
16</environments>
复制代码
当我们需要增加一个环境配置时,只需要复制粘贴一份environment,修改其中属性的值即可。
4.mappers
mappers标签实际上是用于高速mybatis从哪找到我们写好的SQL语句,也就是映射文件。当我们写好一个表对应的mapper.xml时,我们只需要在mappers下增加一个mapper即可。
mappers查找mapper的方式有多种:
1. 根据mapper.xml文件定位:
这些mapper.xml在resources中的某个文件夹xxx中,则用resource属性设置
1<mappers> 2<mapper resource="xxx/AMapper.xml"/> 3<mapper resource="xxx/BMapper.xml"/> 4</mappers> 复制代码
2. 根据映射器接口实现类的完全限定类名:
当我们在这些mapper.xml设置好了namespace之后,我们可以通过映射器接口实现类的全路径类来设置,如在AMapper.xml设置namespace为com.xxx.dao.AMapper类之后,我们在这里可以使用class属性指定查找的mapper,但前提是:
AMapper.xml和AMapper.java必须在同一个包下。
1<mappers> 2<mapper class ="com.xxx.dao.AMapper"/> 3<mapper class ="com.xxx.dao.BMapper"/> 4</mappers> 复制代码
3. 包映射
有人会说,如果我们表有很多,这样一行一行的写不是很费劲吗,mybatis为了便于使用,提供了package的方式引入映射器,但前提
所有的mapper.xml和mapper.java必须在同一个包下。
1<mappers> 2 <package name="org.xxx.dao"/> 3</mappers> 复制代码
4. URL映射:
如果你的mapper不在项目中,而是放到了其他文件内,mybatis提供了通过URL的方式引入mapper.xml。
1<mappers> 2 <mapper url="C:///test/mappers/AMapper.xml"/> 3 <mapper url="C:///test/mappers/BMapper.xml"/> 4</mappers> 复制代码
5. MyBatis的SQL语法
在现有的框架下编写代码,多数情况下都不需要理会mybatis底层的东西,而大量的工作都集中在编写mapper文件上。因此学会在mybatis下编写SQL语句是非常有必要的,我们首先来看一个标准的mapper文件的格式:
1<?xml version="1.0" encoding="UTF-8"?> 2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 3<mapper namespace="com.xxx.dao.XxxMapper"> 4</mapper> 复制代码
可以看出,一个mapper文件的根结构是mapper标签开始,而mapper标签中的namespace有什么用呢?他应该怎么写?
我们知道,有一种编程思想叫做面向接口编程,就是把业务需求中具体逻辑实现和接口分开,对外只暴露接口,通过接口实现业务。而在业务需求变化时,仅需要修改实现类,而不需要变动现有的对接代码,降低对系统的影响。
而mybatis正是基于这样的思想,在namespace中指定该mapper对应的接口之后,不需要编写接口实现类,mybatis会通过该绑定自动帮你找到对应要执行的SQL语句。
如:在com.xxx.dao中创建一个XxxMapper.java的接口,需要编写一根据用户查询用户信息的方法。
1package com.xxx.dao;
2public interface XxxMapper {
3 //根据姓名查询一条用户信息
4 Map selectUserByName(@Param("name") String name);
5}
复制代码
此时我们就可以在mapper.xml中设置namespace对应到上面的接口来:
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3<mapper namespace="com.xxx.dao.XxxMapper">
4 <select id="selectUserByName" parameterType="String" resultType="hashmap">
5 select * from user where name = #{name}
6</select>
7</mapper>
复制代码
而在具体的业务实现类中,则是这样使用的:
1@Service
2public class XxxServiceImpl implements CustomerInfoService {
3 @Resource
4 private XxxMapper xxxMapper=null;
5 @Override
6 public Map getUser(String name) {
7 return xxxMapper.selectUserByName(name);
8 }
9}
复制代码
可以看出,从编写SQL语句到最终业务调用SQL语句的过程中,我们并没有给XxxMapper接口编写任何的实现类,这就是基于接口编程的思想,mybatis已经把这些事情都处理好了,我们只需要在namespace中把SQL映射文件和接口类对应起来,就可以使用了。
知道根节点mapper怎么设置之后,接下来我们需要学习如何在mapper节点里编写SQL语句,在mapper标签后,mybatis提供了很多语义化的标签以便于我们编写SQL语句和配置映射文件,下面是几个非常常用子标签:
1. select:用于编写查询语句的标签
2. update:用于编写update语句的标签
3. insert:用于编写insert语句的标签
4. delete:用于编写delete语句的标签
5. sql:编写语句块的标签,可被其它语句引用
6. resultMap:定义数据库结果和实体属性的映射关系
这些标签都是我们在编写SQL语句中的必备标签,下面一一描述他们的使用。
1. select标签
在一个项目中,大部分功能都涉及到查询,因此mybatis也为select元素配备了非常多的属性,一下仅列出最常用的几个属性以及作用解释:
1<select 2 #必填,唯一标识符,和mapper接口中的方法一一对应 3 id="selectUser" 4 #选填,默认值为 unset,用于传入参数的类型设置 5 parameterType="String" 6 #选填,语句查询结果返回的期望类型,resultType 和 resultMap不能同时使用 7 resultType="HashMap" 8 #选填,语句查询结果返回的数据集,可以对应实体类和和resultMap定义的ID。 9 resultMap="com.xxx.entity.User" 10 #是否清除缓存,为true时本地缓存和二级缓存都会被清除 11 flushCache="false" 12 #是否启用缓存,为true时查询结果会被放入二级缓存 13 useCache="true" > 14 15 #SQL语句编写.... 16 17 </select> 复制代码
2. update标签
1<update 2 #必填,唯一标识符,和mapper接口中的方法一一对应 3 id="updateUser" 4 #选填,默认值为 unset,用于传入参数的类型设置 5 parameterType="com.xxx.entity.User" 6 #是否清除缓存,为true时本地缓存和二级缓存都会被清除 7 flushCache="true"> 8 9 #编写update的SQL语句... 10 11</update> 复制代码
3. insert标签
1<insert 2 #必填,唯一标识符,和mapper接口中的方法一一对应 3 id="updateUser" 4 #选填,默认值为 unset,用于传入参数的类型设置 5 parameterType="com.xxx.entity.User" 6 #是否清除缓存,为true时本地缓存和二级缓存都会被清除 7 flushCache="true" 8 #是否取出由数据库内部生成的主键,默认为false 9 useGeneratedKeys="false" 10 #选填,设置了之后,会通过getGeneratedKeys的返回值或者通过 insert语句的selectKey子元素设置它的键值。 11 keyProperty="id" 12 > 13 14 #编写insert的SQL语句... 15 16</insert> 复制代码
4. delete标签
1<delete 2 #必填,唯一标识符,和mapper接口中的方法一一对应 3 id="updateUser" 4 #选填,默认值为 unset,用于传入参数的类型设置 5 parameterType="com.xxx.entity.User" 6 #是否清除缓存,为true时本地缓存和二级缓存都会被清除 7 flushCache="true"> 8 9 #编写delete的SQL语句... 10 11</delete> 复制代码
5. sql标签
SQL节点用来编写那些可以被重用的SQL代码段,当我们用SQL编写好一个代码段之后,就可以在其他语句使用。
我们都知道,在写满了SQL之后,如果要修改表名,是一件很痛苦的事情,因为表名都写到了SQL语句中了,但是在mybatis中,我们可以利用sql标签来定义好表名,如果在所有的SQL中引入这个代码块即可:
1<sql id="TABLE_NAME">user</sql>
2
3#在语句中用include的方式把表名动态化
4<select id="selectUserByName">
5select * from
6<include refid="TABLE_NAME" />
7 where name = #{name}
8</select>
复制代码
类似的用法还有非常多,比如把查询字段一致的可以用sql块统一定义,然后在需要的地方调用…需要我们在实际使用过程,灵活运用这些标签来减轻SQL的代码量和降低复杂度。
6. resultMap标签
resultMap标签用于表示数据库查询结果和实体对象的映射关系,它是映射文件中中所复杂的一个标签,常用的属性有两个:
1 <resultMap 2 #定义这个resultMap的唯一标识 3 id="XXXResult" 4 #返回值的全限定类名,或类型别名 5 type="com.xxx.entity.User"> 6 7 #子节点.... 8 9 </resultMap> 复制代码
而它的子节点则就非常多了:
1 <resultMap id="XXXResult" type="java.util.HashMap"> 2 #constructor:类在实例化时,用来注入结果到构造方法中 3 <constructor> 4 #idArg:ID参数;标记结果作为ID可以帮助提高整体效能 5 <idArg/> 6 #arg:注入到构造方法的一个普通结果 7 <arg/> 8 </constructor> 9 #一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 10 <id/> 11 #注入到字段或 JavaBean 属性的普通结果 12 <result/> 13 #一个复杂类型的关联;许多结果将包装成这种类型 14 <association property=""/> 15 #一个复杂类型的集合 16 <collection property=""/> 17 # 使用结果值来决定使用哪个 resultMap 18 <discriminator javaType=""> 19 #基于某些值的结果映射 20 <case value=""></case> 21 </discriminator>! 22 </resultMap> 复制代码
如查询要把查询结果的字段用驼峰的写法映射,可以定义一个resultMap,吧对象和实体属性一一对应起来:
1<resultMap id="UserResultMap" type="java.util.HashMap"> 2 <id column="id" property="id"/> 3 <result column="nick_name" property="nickName"/> 4 <result column="gmt_created" property="gmtCreated"/> 5 <result column="gmt_modified" property="gmtModified"/> 6 </resultMap> 复制代码
在SQL用就可以直接使用这个resultMap作为返回类型:
1<select id="selectUserByName" resultMap="UserResultMap">
2 select id,nick_name,gmt_created,gmt_modified from user where name =#{name}
3</select>
复制代码
上面的例子只用到resultMap中最常用的两个子标签: <id>、<result>。还有很多其它的标签可以写成高级的resultMap,由于篇幅较长,而文章旨在入门,因此在此暂不对每个标签举例子解释,有兴趣的可以自行百度。
6. 运行原理和实操一波
看完一波语法之后,脑子处于似懂非懂的状态,好像都是在讲配置文件和mapper的使用。当我们学会了编写这些mapper之后,究竟应该怎么使用它?
到这里我们就不得不提一下mybatis的运行过程了,先了解几个mybatis提供的接口/类:
- SqlSessionFactoryBuilder : SqlSessionFactory的构造器,用于创建SqlSessionFactory,采用了Builder设计模式。
- SqlSessionFactory:SqlSession工厂类,以工厂形式创建SqlSession对象,采用了Factory工厂设计模式。
- SqlSession:执行SQL的接口
由于mybatis的运行原理非常复杂,远远不是30分钟能掌握的,因此在此只是概括为最大的四个过程:
- 加载配置创建SqlSessionFacotry
- 通过sqlSessionFactory获取SqlSession
- SqlSession查找和转化Mapper
- SqlSession执行mapper中的SQL语句
知道了运行流程之后,我们就可以实操一波了,虽然主流的开发框架都已经看不见这些东西了,但作者还是决定抛弃一切框架,只用maven构建一个空白项目进行实操:
- 在idea上创建一个maven项目,并且在pom中引入mybatis和 mysql 依赖
这个简单,不多描述。
其中pom中的依赖为:
1<dependencies> 2 <dependency> 3 <groupId>org.mybatis</groupId> 4 <artifactId>mybatis</artifactId> 5 <version>3.2.7</version> 6 </dependency> 7 <dependency> 8 <groupId>mysql</groupId> 9 <artifactId>mysql-connector-java</artifactId> 10 <version>6.0.6</version> 11 </dependency> 12 </dependencies> 复制代码
- 在resources中创建一个名为mybatis-config.xml的配置文件,内容为:
1<?xml version="1.0" encoding="UTF-8" ?>
2<!DOCTYPE configuration
3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
4<configuration>
5 <!--properties用于定义一些属性变量,以便在配置文件中调用-->
6 <properties>
7 <!--定义一个变量为driver的属性,在下面就可以用${driver}来获得其属性值-->
8 <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
9 <property name="url" value="jdbc:mysql://10.9.0.111/test"></property>
10 </properties>
11 <!--定义不同环境下的配置,便于区分生产、测试等环节的配置-->
12 <environments default="development">
13 <!--定义一个环境下的配置-->
14 <environment id="development">
15 <transactionManager type="JDBC"/>
16 <dataSource type="POOLED">
17 <property name="driver" value="${driver}"/>
18 <property name="url" value="${url}"/>
19 <property name="username" value="root"/>
20 <property name="password" value="test100"/>
21 </dataSource>
22 </environment>
23 </environments>
24 <!--用于设置mapper文件的引入-->
25 <mappers>
26 <!--resource方式引入mapper文件-->
27 <mapper resource="mapper/UserMapper.xml"/>
28 </mappers>
29</configuration>
复制代码
- 创建表结构:
1DROP TABLE IF EXISTS `user`;
2CREATE TABLE `user` (
3 `id` int(11) NOT NULL AUTO_INCREMENT,
4 `name` varchar(255) DEFAULT NULL,
5 `gmt_created` varchar(255) DEFAULT NULL,
6 `gmt_modified` varchar(255) DEFAULT NULL,
7 PRIMARY KEY (`id`)
8) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
9-- 插入一条数
10INSERT INTO `user` VALUES ('1', 'hello mybatis', null, null);
复制代码
- 在java下创建User.java的实体类(注意:为了简化代码,getter和serter已经去掉,实操时自行补上):
1public class User {
2 private Integer id;
3 private String name;
4 private String gmtCreated;
5 private String gmtModified;
6 //getter 和 setter...
7}
复制代码
- 在java下创建UserMapper.java的映射类:
1public interface UserMapper {
2 User getUserByName(@Param("name") String name);
3}
复制代码
- 在resources下创建mapper文件夹,在mapper下创建UserMapper的xml文件:
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
3<mapper namespace="UserMapper">
4 <select id="getUserByName" resultType="User">
5 select * from user where name =#{name}
6 </select>
7</mapper>
复制代码
- 启动mybatis执行SQL
根据上面的运行流程,就可以编写一个测试类:
1 public static void main(String args[]){
2 try {
3 String resource = "mybatis-config.xml";
4// 1. 获取配置文件的输入流
5 InputStream inputStream = Resources.getResourceAsStream(resource);
6// 2. 读取配置文件并用SqlSessionFactoryBuilder创建一个SqlSessionFactory
7 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
8// 3. 从SqlSessionFactory中获取一个SqlSession
9 SqlSession s= sqlSessionFactory.openSession();
10// 4. 查找映射SQL文件
11 UserMapper mapper=s.getMapper(UserMapper.class);
12// 5.执行CURD操作
13 User user=mapper.getUserByName("hello mybatis");
14
15 if(user!=null){
16 System.out.print("查询成功,我的名次是:"+user.getName());
17 }
18
19 }catch (Exception e){
20 e.printStackTrace();
21 }
22 }
复制代码
查看输出:
1查询成功,我的名次是:hello mybatis 复制代码
大功告成!有兴趣的读者可以根据上面的过程,编写属于自己的原生态mybatis的测试项目,如果有问题或者需要源码请关注公众号留言或加微信:sisi-ceo,我们一起来征服写代码这做大山~
觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能
以上所述就是小编给大家介绍的《30分钟入门MyBatis》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- TiDB入门(四):从入门到“跑路”
- MyBatis从入门到精通(一):MyBatis入门
- MyBatis从入门到精通(一):MyBatis入门
- Docker入门(一)用hello world入门docker
- 赵童鞋带你入门PHP(六) ThinkPHP框架入门
- 初学者入门 Golang 的学习型项目,go入门项目
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Usability for the Web
Tom Brinck、Darren Gergle、Scott D. Wood / Morgan Kaufmann / 2001-10-15 / USD 65.95
Every stage in the design of a new web site is an opportunity to meet or miss deadlines and budgetary goals. Every stage is an opportunity to boost or undercut the site's usability. Thi......一起来看看 《Usability for the Web》 这本书的介绍吧!