☻Blog("Laziji")

System.out.print("辣子鸡的博客");

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

传统Mybatis开发使用XML的方式编写SQL脚本进行数据库操作,
Mybatis允许使用动态SQL但是即使如此, 依然存在许多重复性工作, 因为每个基本表的增删改查语句模式其实都是相同的

Mybatis也可以使用Dao接口上以注解的形式编写SQL, 但是也是一样, 必须重复编写, 非常不方便

其实Mybatis已经考虑到了这点, 为我们提供了自定义通用Mapper的实现机制

项目地址

https://github.com/GitHub-Laziji/commons-mybatis

实现

实现方法分为两步

声明引用的方法

在Dao接口的方法上以注解的形式声明, 使用哪个类的哪个方法, 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface DODao<T extends DO> extends Dao<T> {

@SelectProvider(type = SqlProvider.class, method = "selectById")
T selectById(Long id);

@InsertProvider(type = SqlProvider.class, method = "insert")
@Options(useGeneratedKeys = true, keyColumn = "id")
int insert(T bean);

@UpdateProvider(type = SqlProvider.class, method = "update")
int update(T bean);

@DeleteProvider(type = SqlProvider.class, method = "delete")
int delete(Long id);
}

方法实现

在实现方法中可以接收Dao接口中传来的参数, 最后返回一个SQL字符串, 这个SQL可以是动态的, 例如可以使用id=#{id}这样的语法

T selectById(Long id)实现如下

1
2
3
4
5
6
7
8
9
10
public String selectById(ProviderContext context) {
Class clazz = getEntityClass(context);
assert clazz != null;
return new SQL()
.SELECT(getColumns(clazz))
.FROM(getTableName(clazz))
.WHERE("`id`=#{id}")
.toString();
}

我们可以通过context获取Dao的泛型类, 也就是实体类

1
2
3
4
5
6
7
8
9
10
11
12
private Class getEntityClass(ProviderContext context) {
for (Type type : context.getMapperType().getGenericInterfaces()) {
ResolvableType resolvableType = ResolvableType.forType(type);
if (resolvableType.getRawClass() == Dao.class
|| resolvableType.getRawClass() == DODao.class
|| resolvableType.getRawClass() == VODao.class) {
return resolvableType.getGeneric(0).getRawClass();
}
}
return null;
}

通过反射我们可以拿到对应的字段名, 类名, 字段名获取如下, Ignore 是自定义注解, 用于忽略一些字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private String[] getVariables(Class clazz, String[] prefixes) {
List<String> variables = new ArrayList<>();
for (Method method : clazz.getMethods()) {
Ignore annotation = method.getAnnotation(Ignore.class);
if (annotation != null) {
continue;
}
String name = method.getName();
for (String prefix : prefixes) {
int length = prefix.length();
if (name.length() > length && name.startsWith(prefix)
&& name.charAt(length) >= 'A' && name.charAt(length) <= 'Z') {
String variableName = (char) (name.charAt(length) - 'A' + 'a') + name.substring(length + 1);
variables.add(variableName);
break;
}
}

}
return variables.toArray(new String[]{});
}

private String[] getReadVariables(Class clazz) {
return getVariables(clazz, new String[]{"is", "get"});
}

小结

使用通用Mapper无需编写任何SQL 只需创建空Dao 继承通用的Dao<T>即可

RPC是一种远程过程调用, 它是一种通过网络从远程计算机程序上请求服务, 而不需要了解底层网络技术的协议

RPC可以把远程服务像本地服务一样调用, 以Java中为例, 客户端与服务端一般共用一个核心包, 核心包中包含了需要调用服务的接口

在服务端实现这些接口, 客户端通过Socket等方式连接服务端, 发生调用的信息(方法名, 参数等)

服务端接收后执行相应动作, 最后通过网络返回计算结果, 一次RPC调用就完成了

下面是Java中的简单实现

共用接口

1
2
3
public interface TestService {
String print(String s);
}

一个简单的打印服务, 不包含实现

客户端

在客户端代码中自始至终没有编写服务的实现, 只有一个接口, 但是又可以得到服务的实例,

要做到这点需要用到Java中的动态代理Proxy.newProxyInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;

public class Consumer {

public static void main(String[] as) {

TestService service = (TestService) Proxy.newProxyInstance(TestService.class.getClassLoader(), new Class<?>[]{TestService.class}, (Object proxy, Method method, Object[] args) -> {
try(Socket socket = new Socket()){
socket.connect(new InetSocketAddress(12306));
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
os.writeUTF(method.getName());
os.writeObject(method.getParameterTypes());
os.writeObject(args);
return new ObjectInputStream(socket.getInputStream()).readObject();
}catch (Exception e){
return null;
}
});
System.out.println(service.print("abc"));
}
}

通过对象流把参数等信息发送给服务端

服务端

一般服务端是预先实例化服务, 以完整类名为Key把服务存进集合供调用, 或者依赖现成的Springboot等框架管理服务, 下面是服务端的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Producer {

public static void main(String[] args) {

TestService service = new TestServiceImpl();

try (ServerSocket serverSocket = new ServerSocket()){
serverSocket.bind(new InetSocketAddress(12306));

try(Socket accept = serverSocket.accept()){
ObjectInputStream is = new ObjectInputStream(accept.getInputStream());
String methodName = is.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) is.readObject();
Object[] arguments = (Object[]) is.readObject();
Object result = TestService.class.getMethod(methodName,parameterTypes).invoke(service,arguments);
new ObjectOutputStream(accept.getOutputStream()).writeObject(result);
}
} catch (Exception e) {
e.printStackTrace();
}

}
}

服务实例

服务端编写服务实例代码

1
2
3
4
5
6
7
public class TestServiceImpl implements TestService{

@Override
public String print(String s) {
return "**"+s+"**";
}
}

结果

至此可以看到客户端调用的

1
System.out.println(service.print("abc"));

打印出了

1
**abc**

就好像调用了本地服务一样

0%